diff --git a/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx b/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx index b344764..32db1f8 100644 --- a/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx +++ b/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx @@ -5,6 +5,7 @@ import { Service, UptimeData } from "@/types/service.types"; import { useToast } from "@/hooks/use-toast"; import { useNavigate } from "react-router-dom"; import { uptimeService } from "@/services/uptimeService"; +import { DateRangeOption } from "../../DateRangeFilter"; export const useServiceData = (serviceId: string | undefined, startDate: Date, endDate: Date) => { const [service, setService] = useState(null); @@ -13,15 +14,12 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e const { toast } = useToast(); const navigate = useNavigate(); - // Handler for service status changes const handleStatusChange = async (newStatus: "up" | "down" | "paused" | "warning") => { if (!service || !serviceId) return; try { - // Optimistic UI update setService({ ...service, status: newStatus as Service["status"] }); - // Update the service status in PocketBase await pb.collection('services').update(serviceId, { status: newStatus }); @@ -32,7 +30,6 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e }); } catch (error) { console.error("Failed to update service status:", error); - // Revert the optimistic update setService(prevService => prevService); toast({ @@ -43,51 +40,30 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e } }; - // Function to fetch uptime data with date filters - const fetchUptimeData = async (serviceId: string, start: Date, end: Date, selectedRange: string) => { + const fetchUptimeData = async (serviceId: string, start: Date, end: Date, selectedRange?: DateRangeOption | string) => { try { - console.log(`Fetching uptime data from ${start.toISOString()} to ${end.toISOString()}`); + console.log(`Fetching uptime data: ${start.toISOString()} to ${end.toISOString()} for range: ${selectedRange}`); - // Set appropriate limits based on time range to ensure enough granularity - let limit = 200; // default + let limit = 500; // Default limit - // Adjust limits based on selected range - if (selectedRange === '60min') { - limit = 300; // More points for shorter time ranges - } else if (selectedRange === '24h') { - limit = 200; + if (selectedRange === '24h') { + limit = 300; } else if (selectedRange === '7d') { - limit = 250; - } else if (selectedRange === '30d' || selectedRange === '1y') { - limit = 300; // More points for longer time ranges + limit = 400; } console.log(`Using limit ${limit} for range ${selectedRange}`); const history = await uptimeService.getUptimeHistory(serviceId, limit, start, end); - console.log(`Fetched ${history.length} uptime records for time range ${selectedRange}`); + console.log(`Retrieved ${history.length} uptime records`); - if (history.length === 0) { - console.log("No data returned from API, checking if we need to fetch with a higher limit"); - // If no data, try with a higher limit as fallback - if (limit < 500) { - const extendedHistory = await uptimeService.getUptimeHistory(serviceId, 500, start, end); - console.log(`Fallback: Fetched ${extendedHistory.length} uptime records with higher limit`); - - if (extendedHistory.length > 0) { - setUptimeData(extendedHistory); - return extendedHistory; - } - } - } - - // Sort data by timestamp (newest first) - const sortedHistory = [...history].sort((a, b) => + // Sort by timestamp (newest first) + const filteredHistory = [...history].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() ); - setUptimeData(sortedHistory); - return sortedHistory; + setUptimeData(filteredHistory); + return filteredHistory; } catch (error) { console.error("Error fetching uptime data:", error); toast({ @@ -110,7 +86,6 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e setIsLoading(true); - // Add a timeout to prevent hanging const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("Request timed out")), 10000); }); @@ -136,7 +111,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e setService(formattedService); - // Fetch uptime history with date range + // Fetch initial uptime history with 24h default await fetchUptimeData(serviceId, startDate, endDate, '24h'); } catch (error) { console.error("Error fetching service:", error); @@ -156,10 +131,11 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e // Update data when date range changes useEffect(() => { - if (serviceId && !isLoading) { - fetchUptimeData(serviceId, startDate, endDate, '24h'); + if (serviceId && !isLoading && service) { + console.log(`Date range changed, refetching data for ${serviceId}: ${startDate.toISOString()} to ${endDate.toISOString()}`); + fetchUptimeData(serviceId, startDate, endDate); } - }, [startDate, endDate]); + }, [startDate, endDate, serviceId, isLoading, service]); return { service, @@ -170,4 +146,4 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e handleStatusChange, fetchUptimeData }; -}; +}; \ No newline at end of file diff --git a/application/src/components/services/ServiceDetailContainer/index.tsx b/application/src/components/services/ServiceDetailContainer/index.tsx index 2bd7d2c..e23e054 100644 --- a/application/src/components/services/ServiceDetailContainer/index.tsx +++ b/application/src/components/services/ServiceDetailContainer/index.tsx @@ -1,4 +1,3 @@ - import { useState, useEffect, useCallback } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { DateRangeOption } from "../DateRangeFilter"; @@ -12,13 +11,17 @@ export const ServiceDetailContainer = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); - // Ensure we use exact timestamp for startDate + // Set default to 24h const [startDate, setStartDate] = useState(() => { const date = new Date(); - date.setHours(date.getHours() - 24); + date.setHours(date.getHours() - 24); // Go back 24 hours + return date; + }); + const [endDate, setEndDate] = useState(() => { + const date = new Date(); + date.setMinutes(date.getMinutes() + 5); // Add 5 minutes buffer to future return date; }); - const [endDate, setEndDate] = useState(new Date()); const [selectedRange, setSelectedRange] = useState('24h'); // State for sidebar collapse functionality (shared with Dashboard) @@ -91,14 +94,16 @@ export const ServiceDetailContainer = () => { // Handle date range filter changes const handleDateRangeChange = useCallback((start: Date, end: Date, option: DateRangeOption) => { - console.log(`Date range changed: ${start.toISOString()} to ${end.toISOString()}, option: ${option}`); + console.log(`ServiceDetailContainer: Date range changed: ${start.toISOString()} to ${end.toISOString()}, option: ${option}`); + // Update state which will trigger the useEffect in useServiceData setStartDate(start); setEndDate(end); setSelectedRange(option); - // Refetch uptime data with new date range, passing the selected range option + // Also explicitly fetch data with the new range to ensure immediate update if (id) { + console.log(`ServiceDetailContainer: Explicitly fetching data for service ${id} with new range`); fetchUptimeData(id, start, end, option); } }, [id, fetchUptimeData]); @@ -123,4 +128,4 @@ export const ServiceDetailContainer = () => { )} ); -}; +}; \ No newline at end of file diff --git a/application/src/services/uptimeService.ts b/application/src/services/uptimeService.ts index 33325de..d2e5a57 100644 --- a/application/src/services/uptimeService.ts +++ b/application/src/services/uptimeService.ts @@ -2,29 +2,24 @@ import { pb } from '@/lib/pocketbase'; import { UptimeData } from '@/types/service.types'; -// Simple in-memory cache to avoid excessive requests const uptimeCache = new Map(); -// Cache time-to-live in milliseconds -const CACHE_TTL = 15000; // 15 seconds +const CACHE_TTL = 3000; // 3 seconds for faster updates export const uptimeService = { async recordUptimeData(data: UptimeData): Promise { try { console.log(`Recording uptime data for service ${data.serviceId}: Status ${data.status}, Response time: ${data.responseTime}ms`); - // Create a custom request options object to disable auto-cancellation const options = { - $autoCancel: false, // Disable auto-cancellation for this request - $cancelKey: `uptime_record_${data.serviceId}_${Date.now()}` // Unique key for this request + $autoCancel: false, + $cancelKey: `uptime_record_${data.serviceId}_${Date.now()}` }; - // Store uptime history in the uptime_data collection - // Format data for PocketBase (use snake_case) const record = await pb.collection('uptime_data').create({ service_id: data.serviceId, timestamp: data.timestamp, @@ -32,8 +27,9 @@ export const uptimeService = { response_time: data.responseTime }, options); - // Invalidate cache for this service after recording new data - uptimeCache.delete(`uptime_${data.serviceId}`); + // Invalidate cache for this service + const keysToDelete = Array.from(uptimeCache.keys()).filter(key => key.includes(`uptime_${data.serviceId}`)); + keysToDelete.forEach(key => uptimeCache.delete(key)); console.log(`Uptime data recorded successfully with ID: ${record.id}`); } catch (error) { @@ -49,10 +45,9 @@ export const uptimeService = { endDate?: Date ): Promise { try { - // Create cache key based on parameters const cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}`; - // Check if we have a valid cached result + // Check cache const cached = uptimeCache.get(cacheKey); if (cached && (Date.now() - cached.timestamp) < cached.expiresIn) { console.log(`Using cached uptime history for service ${serviceId}`); @@ -61,80 +56,74 @@ export const uptimeService = { console.log(`Fetching uptime history for service ${serviceId}, limit: ${limit}`); - // Base filter for a specific service let filter = `service_id='${serviceId}'`; // Add date range filtering if provided if (startDate && endDate) { - // Convert to ISO strings for PocketBase filtering - const startISO = startDate.toISOString(); - const endISO = endDate.toISOString(); + // Convert dates to UTC strings in the format PocketBase expects + const startUTC = startDate.toISOString(); + const endUTC = endDate.toISOString(); - // Log the date range we're filtering by - console.log(`Date range filter: ${startISO} to ${endISO}`); + console.log(`Date filter: ${startUTC} to ${endUTC}`); - filter += ` && timestamp >= '${startISO}' && timestamp <= '${endISO}'`; + // Use proper PocketBase date filtering syntax + filter += ` && timestamp >= "${startUTC}" && timestamp <= "${endUTC}"`; } - // Calculate time difference to determine if it's a short range (like 60min) - const isShortTimeRange = startDate && endDate && - (endDate.getTime() - startDate.getTime() <= 60 * 60 * 1000); - - // For very short time ranges, adjust sorting and limit - const sort = '-timestamp'; // Default: newest first - const actualLimit = isShortTimeRange ? Math.max(limit, 300) : limit; // Ensure adequate points for short ranges - - // Create custom request options to disable auto-cancellation const options = { filter: filter, - sort: sort, - $autoCancel: false, // Disable auto-cancellation for this request - $cancelKey: `uptime_history_${serviceId}_${Date.now()}` // Unique key for this request + sort: '-timestamp', + $autoCancel: false, + $cancelKey: `uptime_history_${serviceId}_${Date.now()}` }; - try { - // Get uptime history for a specific service with date filtering - const response = await pb.collection('uptime_data').getList(1, actualLimit, options); + console.log(`Filter query: ${filter}`); + + const response = await pb.collection('uptime_data').getList(1, limit, options); + + console.log(`Fetched ${response.items.length} uptime records for service ${serviceId}`); + + if (response.items.length > 0) { + console.log(`Date range in results: ${response.items[response.items.length - 1].timestamp} to ${response.items[0].timestamp}`); + } else { + console.log(`No records found for filter: ${filter}`); - console.log(`Fetched ${response.items.length} uptime records for service ${serviceId}`); - - // Map and return the data - const uptimeData = response.items.map(item => ({ - id: item.id, - serviceId: item.service_id, - timestamp: item.timestamp, - status: item.status, - responseTime: item.response_time || 0, - date: item.timestamp, // Mapping timestamp to date - uptime: 100 // Default value for uptime - })); - - // For short time ranges with few data points, we might need to ensure we have enough points - const shouldAddPlaceholderData = isShortTimeRange && uptimeData.length <= 2; - - let finalData = uptimeData; - - if (shouldAddPlaceholderData && uptimeData.length > 0) { - // We'll add some additional data points to ensure graph is visible - console.log("Adding placeholder data points to ensure graph visibility"); - finalData = [...uptimeData]; - } - - // Cache the result - uptimeCache.set(cacheKey, { - data: finalData, - timestamp: Date.now(), - expiresIn: CACHE_TTL + // Try a fallback query without date filter to see if there's any data at all + const fallbackResponse = await pb.collection('uptime_data').getList(1, 10, { + filter: `service_id='${serviceId}'`, + sort: '-timestamp', + $autoCancel: false }); - return finalData; - } catch (err) { - throw err; + console.log(`Fallback query found ${fallbackResponse.items.length} total records for service`); + if (fallbackResponse.items.length > 0) { + console.log(`Latest record timestamp: ${fallbackResponse.items[0].timestamp}`); + console.log(`Oldest record timestamp: ${fallbackResponse.items[fallbackResponse.items.length - 1].timestamp}`); + } } + + const uptimeData = response.items.map(item => ({ + id: item.id, + serviceId: item.service_id, + timestamp: item.timestamp, + status: item.status, + responseTime: item.response_time || 0, + date: item.timestamp, + uptime: 100 + })); + + // Cache the result + uptimeCache.set(cacheKey, { + data: uptimeData, + timestamp: Date.now(), + expiresIn: CACHE_TTL + }); + + return uptimeData; } catch (error) { console.error("Error fetching uptime history:", error); - // Try to return cached data even if it's expired, as a fallback + // Try to return cached data as fallback const cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}`; const cached = uptimeCache.get(cacheKey); if (cached) { @@ -145,4 +134,4 @@ export const uptimeService = { throw new Error('Failed to load uptime history.'); } } -}; +}; \ No newline at end of file