Adjusted date range calculation to ensure data is displayed

This commit is contained in:
Tola Leng
2025-05-29 20:10:29 +08:00
parent d4d4040cc8
commit 0562adbef8
3 changed files with 87 additions and 117 deletions
@@ -5,6 +5,7 @@ import { Service, UptimeData } from "@/types/service.types";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { uptimeService } from "@/services/uptimeService"; import { uptimeService } from "@/services/uptimeService";
import { DateRangeOption } from "../../DateRangeFilter";
export const useServiceData = (serviceId: string | undefined, startDate: Date, endDate: Date) => { export const useServiceData = (serviceId: string | undefined, startDate: Date, endDate: Date) => {
const [service, setService] = useState<Service | null>(null); const [service, setService] = useState<Service | null>(null);
@@ -13,15 +14,12 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
const { toast } = useToast(); const { toast } = useToast();
const navigate = useNavigate(); const navigate = useNavigate();
// Handler for service status changes
const handleStatusChange = async (newStatus: "up" | "down" | "paused" | "warning") => { const handleStatusChange = async (newStatus: "up" | "down" | "paused" | "warning") => {
if (!service || !serviceId) return; if (!service || !serviceId) return;
try { try {
// Optimistic UI update
setService({ ...service, status: newStatus as Service["status"] }); setService({ ...service, status: newStatus as Service["status"] });
// Update the service status in PocketBase
await pb.collection('services').update(serviceId, { await pb.collection('services').update(serviceId, {
status: newStatus status: newStatus
}); });
@@ -32,7 +30,6 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
}); });
} catch (error) { } catch (error) {
console.error("Failed to update service status:", error); console.error("Failed to update service status:", error);
// Revert the optimistic update
setService(prevService => prevService); setService(prevService => prevService);
toast({ 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?: DateRangeOption | string) => {
const fetchUptimeData = async (serviceId: string, start: Date, end: Date, selectedRange: string) => {
try { 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 = 500; // Default limit
let limit = 200; // default
// Adjust limits based on selected range if (selectedRange === '24h') {
if (selectedRange === '60min') { limit = 300;
limit = 300; // More points for shorter time ranges
} else if (selectedRange === '24h') {
limit = 200;
} else if (selectedRange === '7d') { } else if (selectedRange === '7d') {
limit = 250; limit = 400;
} else if (selectedRange === '30d' || selectedRange === '1y') {
limit = 300; // More points for longer time ranges
} }
console.log(`Using limit ${limit} for range ${selectedRange}`); console.log(`Using limit ${limit} for range ${selectedRange}`);
const history = await uptimeService.getUptimeHistory(serviceId, limit, start, end); 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) { // Sort by timestamp (newest first)
console.log("No data returned from API, checking if we need to fetch with a higher limit"); const filteredHistory = [...history].sort((a, b) =>
// 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) =>
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
); );
setUptimeData(sortedHistory); setUptimeData(filteredHistory);
return sortedHistory; return filteredHistory;
} catch (error) { } catch (error) {
console.error("Error fetching uptime data:", error); console.error("Error fetching uptime data:", error);
toast({ toast({
@@ -110,7 +86,6 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
setIsLoading(true); setIsLoading(true);
// Add a timeout to prevent hanging
const timeoutPromise = new Promise((_, reject) => { const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Request timed out")), 10000); setTimeout(() => reject(new Error("Request timed out")), 10000);
}); });
@@ -136,7 +111,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e
setService(formattedService); setService(formattedService);
// Fetch uptime history with date range // Fetch initial uptime history with 24h default
await fetchUptimeData(serviceId, startDate, endDate, '24h'); await fetchUptimeData(serviceId, startDate, endDate, '24h');
} catch (error) { } catch (error) {
console.error("Error fetching service:", 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 // Update data when date range changes
useEffect(() => { useEffect(() => {
if (serviceId && !isLoading) { if (serviceId && !isLoading && service) {
fetchUptimeData(serviceId, startDate, endDate, '24h'); 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 { return {
service, service,
@@ -1,4 +1,3 @@
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate } from "react-router-dom";
import { DateRangeOption } from "../DateRangeFilter"; import { DateRangeOption } from "../DateRangeFilter";
@@ -12,13 +11,17 @@ export const ServiceDetailContainer = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
// Ensure we use exact timestamp for startDate // Set default to 24h
const [startDate, setStartDate] = useState<Date>(() => { const [startDate, setStartDate] = useState<Date>(() => {
const date = new Date(); const date = new Date();
date.setHours(date.getHours() - 24); date.setHours(date.getHours() - 24); // Go back 24 hours
return date;
});
const [endDate, setEndDate] = useState<Date>(() => {
const date = new Date();
date.setMinutes(date.getMinutes() + 5); // Add 5 minutes buffer to future
return date; return date;
}); });
const [endDate, setEndDate] = useState<Date>(new Date());
const [selectedRange, setSelectedRange] = useState<DateRangeOption>('24h'); const [selectedRange, setSelectedRange] = useState<DateRangeOption>('24h');
// State for sidebar collapse functionality (shared with Dashboard) // State for sidebar collapse functionality (shared with Dashboard)
@@ -91,14 +94,16 @@ export const ServiceDetailContainer = () => {
// Handle date range filter changes // Handle date range filter changes
const handleDateRangeChange = useCallback((start: Date, end: Date, option: DateRangeOption) => { 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); setStartDate(start);
setEndDate(end); setEndDate(end);
setSelectedRange(option); 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) { if (id) {
console.log(`ServiceDetailContainer: Explicitly fetching data for service ${id} with new range`);
fetchUptimeData(id, start, end, option); fetchUptimeData(id, start, end, option);
} }
}, [id, fetchUptimeData]); }, [id, fetchUptimeData]);
+53 -64
View File
@@ -2,29 +2,24 @@
import { pb } from '@/lib/pocketbase'; import { pb } from '@/lib/pocketbase';
import { UptimeData } from '@/types/service.types'; import { UptimeData } from '@/types/service.types';
// Simple in-memory cache to avoid excessive requests
const uptimeCache = new Map<string, { const uptimeCache = new Map<string, {
data: UptimeData[], data: UptimeData[],
timestamp: number, timestamp: number,
expiresIn: number expiresIn: number
}>(); }>();
// Cache time-to-live in milliseconds const CACHE_TTL = 3000; // 3 seconds for faster updates
const CACHE_TTL = 15000; // 15 seconds
export const uptimeService = { export const uptimeService = {
async recordUptimeData(data: UptimeData): Promise<void> { async recordUptimeData(data: UptimeData): Promise<void> {
try { try {
console.log(`Recording uptime data for service ${data.serviceId}: Status ${data.status}, Response time: ${data.responseTime}ms`); 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 = { const options = {
$autoCancel: false, // Disable auto-cancellation for this request $autoCancel: false,
$cancelKey: `uptime_record_${data.serviceId}_${Date.now()}` // Unique key for this request $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({ const record = await pb.collection('uptime_data').create({
service_id: data.serviceId, service_id: data.serviceId,
timestamp: data.timestamp, timestamp: data.timestamp,
@@ -32,8 +27,9 @@ export const uptimeService = {
response_time: data.responseTime response_time: data.responseTime
}, options); }, options);
// Invalidate cache for this service after recording new data // Invalidate cache for this service
uptimeCache.delete(`uptime_${data.serviceId}`); 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}`); console.log(`Uptime data recorded successfully with ID: ${record.id}`);
} catch (error) { } catch (error) {
@@ -49,10 +45,9 @@ export const uptimeService = {
endDate?: Date endDate?: Date
): Promise<UptimeData[]> { ): Promise<UptimeData[]> {
try { try {
// Create cache key based on parameters
const cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}`; const cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}`;
// Check if we have a valid cached result // Check cache
const cached = uptimeCache.get(cacheKey); const cached = uptimeCache.get(cacheKey);
if (cached && (Date.now() - cached.timestamp) < cached.expiresIn) { if (cached && (Date.now() - cached.timestamp) < cached.expiresIn) {
console.log(`Using cached uptime history for service ${serviceId}`); 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}`); console.log(`Fetching uptime history for service ${serviceId}, limit: ${limit}`);
// Base filter for a specific service
let filter = `service_id='${serviceId}'`; let filter = `service_id='${serviceId}'`;
// Add date range filtering if provided // Add date range filtering if provided
if (startDate && endDate) { if (startDate && endDate) {
// Convert to ISO strings for PocketBase filtering // Convert dates to UTC strings in the format PocketBase expects
const startISO = startDate.toISOString(); const startUTC = startDate.toISOString();
const endISO = endDate.toISOString(); const endUTC = endDate.toISOString();
// Log the date range we're filtering by console.log(`Date filter: ${startUTC} to ${endUTC}`);
console.log(`Date range filter: ${startISO} to ${endISO}`);
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 = { const options = {
filter: filter, filter: filter,
sort: sort, sort: '-timestamp',
$autoCancel: false, // Disable auto-cancellation for this request $autoCancel: false,
$cancelKey: `uptime_history_${serviceId}_${Date.now()}` // Unique key for this request $cancelKey: `uptime_history_${serviceId}_${Date.now()}`
}; };
try { console.log(`Filter query: ${filter}`);
// Get uptime history for a specific service with date filtering
const response = await pb.collection('uptime_data').getList(1, actualLimit, options);
console.log(`Fetched ${response.items.length} uptime records for service ${serviceId}`); const response = await pb.collection('uptime_data').getList(1, limit, options);
// Map and return the data console.log(`Fetched ${response.items.length} uptime records for service ${serviceId}`);
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 if (response.items.length > 0) {
const shouldAddPlaceholderData = isShortTimeRange && uptimeData.length <= 2; 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}`);
let finalData = uptimeData; // 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, {
if (shouldAddPlaceholderData && uptimeData.length > 0) { filter: `service_id='${serviceId}'`,
// We'll add some additional data points to ensure graph is visible sort: '-timestamp',
console.log("Adding placeholder data points to ensure graph visibility"); $autoCancel: false
finalData = [...uptimeData];
}
// Cache the result
uptimeCache.set(cacheKey, {
data: finalData,
timestamp: Date.now(),
expiresIn: CACHE_TTL
}); });
return finalData; console.log(`Fallback query found ${fallbackResponse.items.length} total records for service`);
} catch (err) { if (fallbackResponse.items.length > 0) {
throw err; 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) { } catch (error) {
console.error("Error fetching uptime history:", 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 cacheKey = `uptime_${serviceId}_${limit}_${startDate?.toISOString() || ''}_${endDate?.toISOString() || ''}`;
const cached = uptimeCache.get(cacheKey); const cached = uptimeCache.get(cacheKey);
if (cached) { if (cached) {