@checkstack/healthcheck-frontend 0.4.9 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +105 -0
- package/package.json +1 -1
- package/src/auto-charts/useStrategySchemas.ts +18 -6
- package/src/components/AggregatedDataBanner.tsx +51 -8
- package/src/components/HealthCheckDiagram.tsx +9 -12
- package/src/components/HealthCheckEditor.tsx +4 -4
- package/src/components/HealthCheckLatencyChart.tsx +6 -3
- package/src/components/HealthCheckStatusTimeline.tsx +6 -3
- package/src/components/HealthCheckSystemOverview.tsx +11 -15
- package/src/hooks/useHealthCheckData.ts +10 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,110 @@
|
|
|
1
1
|
# @checkstack/healthcheck-frontend
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ac3a4cf: ### Dynamic Bucket Sizing for Health Check Visualization
|
|
8
|
+
|
|
9
|
+
Implements industry-standard dynamic bucket sizing for health check data aggregation, following patterns from Grafana/VictoriaMetrics.
|
|
10
|
+
|
|
11
|
+
**What changed:**
|
|
12
|
+
|
|
13
|
+
- Replaced fixed `bucketSize: "hourly" | "daily" | "auto"` with dynamic `targetPoints` parameter (default: 500)
|
|
14
|
+
- Bucket interval is now calculated as `(endDate - startDate) / targetPoints` with a minimum of 1 second
|
|
15
|
+
- Added `bucketIntervalSeconds` to aggregated response and individual buckets
|
|
16
|
+
- Updated chart components to use dynamic time formatting based on bucket interval
|
|
17
|
+
|
|
18
|
+
**Why:**
|
|
19
|
+
|
|
20
|
+
- A 24-hour view with 1-second health checks previously returned 86,400+ data points, causing lag
|
|
21
|
+
- Now returns ~500 data points regardless of timeframe, ensuring consistent chart performance
|
|
22
|
+
- Charts still preserve visual fidelity through proper aggregation
|
|
23
|
+
|
|
24
|
+
**Breaking Change:**
|
|
25
|
+
|
|
26
|
+
- `bucketSize` parameter removed from `getAggregatedHistory` and `getDetailedAggregatedHistory` endpoints
|
|
27
|
+
- Use `targetPoints` instead (defaults to 500 if not specified)
|
|
28
|
+
|
|
29
|
+
***
|
|
30
|
+
|
|
31
|
+
### Collector Aggregated Charts Fix
|
|
32
|
+
|
|
33
|
+
Fixed issue where collector auto-charts (like HTTP request response time charts) were not showing in aggregated data mode.
|
|
34
|
+
|
|
35
|
+
**What changed:**
|
|
36
|
+
|
|
37
|
+
- Added `aggregatedResultSchema` to `CollectorDtoSchema`
|
|
38
|
+
- Backend now returns collector aggregated schemas via `getCollectors` endpoint
|
|
39
|
+
- Frontend `useStrategySchemas` hook now merges collector aggregated schemas
|
|
40
|
+
- Service now calls each collector's `aggregateResult()` when building buckets
|
|
41
|
+
- Aggregated collector data stored in `aggregatedResult.collectors[uuid]`
|
|
42
|
+
|
|
43
|
+
**Why:**
|
|
44
|
+
|
|
45
|
+
- Previously only strategy-level aggregated results were computed
|
|
46
|
+
- Collectors like HTTP Request Collector have their own `aggregateResult` method
|
|
47
|
+
- Without calling these, fields like `avgResponseTimeMs` and `successRate` were missing from aggregated buckets
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- 095cf4e: ### Cross-Tier Data Aggregation
|
|
52
|
+
|
|
53
|
+
Implements intelligent cross-tier querying for health check history, enabling seamless data retrieval across raw, hourly, and daily storage tiers.
|
|
54
|
+
|
|
55
|
+
**What changed:**
|
|
56
|
+
|
|
57
|
+
- `getAggregatedHistory` now queries all three tiers (raw, hourly, daily) in parallel
|
|
58
|
+
- Added `NormalizedBucket` type for unified bucket format across tiers
|
|
59
|
+
- Added `mergeTieredBuckets()` to merge data with priority (raw > hourly > daily)
|
|
60
|
+
- Added `combineBuckets()` and `reaggregateBuckets()` for re-aggregation to target bucket size
|
|
61
|
+
- Raw data preserves full granularity when available (uses target bucket interval)
|
|
62
|
+
|
|
63
|
+
**Why:**
|
|
64
|
+
|
|
65
|
+
- Previously, the API only queried raw runs, which are retained for a limited period (default 7 days)
|
|
66
|
+
- For longer time ranges, data was missing because hourly/daily aggregates weren't queried
|
|
67
|
+
- The retention job only runs periodically, so we can't assume tier boundaries based on config
|
|
68
|
+
- Querying all tiers ensures no gaps in data coverage
|
|
69
|
+
|
|
70
|
+
**Technical details:**
|
|
71
|
+
|
|
72
|
+
- Additive metrics (counts, latencySum) are summed correctly for accurate averages
|
|
73
|
+
- p95 latency uses max of source p95s as conservative upper-bound approximation
|
|
74
|
+
- `aggregatedResult` (strategy-specific) is preserved for raw-only buckets
|
|
75
|
+
|
|
76
|
+
- 538e45d: Fixed 24-hour date range not returning correct data and improved chart display
|
|
77
|
+
|
|
78
|
+
- Fixed missing `endDate` parameter in raw data queries causing data to extend beyond selected time range
|
|
79
|
+
- Fixed incorrect 24-hour date calculation using `setHours()` - now uses `date-fns` `subHours()` for correct date math
|
|
80
|
+
- Refactored `DateRangePreset` from string union to enum for improved type safety and IDE support
|
|
81
|
+
- Exported `getPresetRange` function for reuse across components
|
|
82
|
+
- Changed chart x-axis domain from `["auto", "auto"]` to `["dataMin", "dataMax"]` to remove padding gaps
|
|
83
|
+
|
|
84
|
+
- Updated dependencies [ac3a4cf]
|
|
85
|
+
- Updated dependencies [db1f56f]
|
|
86
|
+
- Updated dependencies [538e45d]
|
|
87
|
+
- @checkstack/healthcheck-common@0.5.0
|
|
88
|
+
- @checkstack/common@0.6.0
|
|
89
|
+
- @checkstack/ui@0.4.1
|
|
90
|
+
- @checkstack/dashboard-frontend@0.3.9
|
|
91
|
+
- @checkstack/auth-frontend@0.5.4
|
|
92
|
+
- @checkstack/catalog-common@1.2.4
|
|
93
|
+
- @checkstack/frontend-api@0.3.3
|
|
94
|
+
- @checkstack/signal-frontend@0.0.10
|
|
95
|
+
|
|
96
|
+
## 0.4.10
|
|
97
|
+
|
|
98
|
+
### Patch Changes
|
|
99
|
+
|
|
100
|
+
- d1324e6: Removed redundant inner scroll wrapper from HealthCheckEditor - Dialog now handles scrolling
|
|
101
|
+
- Updated dependencies [d1324e6]
|
|
102
|
+
- Updated dependencies [1f1f6c2]
|
|
103
|
+
- Updated dependencies [2c0822d]
|
|
104
|
+
- @checkstack/ui@0.4.0
|
|
105
|
+
- @checkstack/dashboard-frontend@0.3.8
|
|
106
|
+
- @checkstack/auth-frontend@0.5.3
|
|
107
|
+
|
|
3
108
|
## 0.4.9
|
|
4
109
|
|
|
5
110
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -38,7 +38,7 @@ export function useStrategySchemas(strategyId: string): {
|
|
|
38
38
|
// Fetch collectors with useQuery
|
|
39
39
|
const { data: collectors } = healthCheckClient.getCollectors.useQuery(
|
|
40
40
|
{ strategyId },
|
|
41
|
-
{ enabled: !!strategyId }
|
|
41
|
+
{ enabled: !!strategyId },
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
useEffect(() => {
|
|
@@ -51,22 +51,34 @@ export function useStrategySchemas(strategyId: string): {
|
|
|
51
51
|
if (strategy) {
|
|
52
52
|
// Build collector schemas object for nesting under resultSchema.properties.collectors
|
|
53
53
|
const collectorProperties: Record<string, unknown> = {};
|
|
54
|
+
const collectorAggregatedProperties: Record<string, unknown> = {};
|
|
55
|
+
|
|
54
56
|
for (const collector of collectors) {
|
|
55
57
|
// Use full ID so it matches stored data keys like "healthcheck-http.request"
|
|
56
58
|
collectorProperties[collector.id] = collector.resultSchema;
|
|
59
|
+
|
|
60
|
+
// Also collect aggregated schemas if available
|
|
61
|
+
if (collector.aggregatedResultSchema) {
|
|
62
|
+
collectorAggregatedProperties[collector.id] =
|
|
63
|
+
collector.aggregatedResultSchema;
|
|
64
|
+
}
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
// Merge collector schemas into strategy result schema
|
|
60
68
|
const mergedResultSchema = mergeCollectorSchemas(
|
|
61
69
|
strategy.resultSchema as Record<string, unknown> | undefined,
|
|
62
|
-
collectorProperties
|
|
70
|
+
collectorProperties,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Merge collector aggregated schemas into strategy aggregated schema
|
|
74
|
+
const mergedAggregatedSchema = mergeCollectorSchemas(
|
|
75
|
+
strategy.aggregatedResultSchema as Record<string, unknown> | undefined,
|
|
76
|
+
collectorAggregatedProperties,
|
|
63
77
|
);
|
|
64
78
|
|
|
65
79
|
setSchemas({
|
|
66
80
|
resultSchema: mergedResultSchema,
|
|
67
|
-
aggregatedResultSchema:
|
|
68
|
-
(strategy.aggregatedResultSchema as Record<string, unknown>) ??
|
|
69
|
-
undefined,
|
|
81
|
+
aggregatedResultSchema: mergedAggregatedSchema,
|
|
70
82
|
});
|
|
71
83
|
}
|
|
72
84
|
|
|
@@ -85,7 +97,7 @@ export function useStrategySchemas(strategyId: string): {
|
|
|
85
97
|
*/
|
|
86
98
|
function mergeCollectorSchemas(
|
|
87
99
|
strategySchema: Record<string, unknown> | undefined,
|
|
88
|
-
collectorProperties: Record<string, unknown
|
|
100
|
+
collectorProperties: Record<string, unknown>,
|
|
89
101
|
): Record<string, unknown> | undefined {
|
|
90
102
|
// If no collectors, return original schema
|
|
91
103
|
if (Object.keys(collectorProperties).length === 0) {
|
|
@@ -1,24 +1,67 @@
|
|
|
1
1
|
import { InfoBanner } from "@checkstack/ui";
|
|
2
2
|
|
|
3
3
|
interface AggregatedDataBannerProps {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/** Bucket interval in seconds */
|
|
5
|
+
bucketIntervalSeconds: number;
|
|
6
|
+
/** The configured check interval in seconds (optional, for comparison) */
|
|
7
|
+
checkIntervalSeconds?: number;
|
|
6
8
|
}
|
|
7
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Format seconds into a human-readable duration string.
|
|
12
|
+
*/
|
|
13
|
+
function formatDuration(seconds: number): string {
|
|
14
|
+
if (seconds < 60) {
|
|
15
|
+
return `${seconds}s`;
|
|
16
|
+
}
|
|
17
|
+
if (seconds < 3600) {
|
|
18
|
+
const mins = Math.round(seconds / 60);
|
|
19
|
+
return `${mins}min`;
|
|
20
|
+
}
|
|
21
|
+
const hours = Math.round(seconds / 3600);
|
|
22
|
+
return `${hours}h`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get resolution tier label based on bucket interval.
|
|
27
|
+
*/
|
|
28
|
+
function getResolutionTier(
|
|
29
|
+
bucketIntervalSeconds: number,
|
|
30
|
+
): "high" | "medium" | "low" {
|
|
31
|
+
if (bucketIntervalSeconds >= 86_400) {
|
|
32
|
+
return "low"; // Daily aggregates
|
|
33
|
+
}
|
|
34
|
+
if (bucketIntervalSeconds >= 3600) {
|
|
35
|
+
return "medium"; // Hourly aggregates
|
|
36
|
+
}
|
|
37
|
+
return "high"; // Raw data
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const TIER_LABELS: Record<"high" | "medium" | "low", string> = {
|
|
41
|
+
high: "High resolution",
|
|
42
|
+
medium: "Medium resolution",
|
|
43
|
+
low: "Low resolution",
|
|
44
|
+
};
|
|
45
|
+
|
|
8
46
|
/**
|
|
9
47
|
* Banner shown when viewing aggregated health check data.
|
|
10
|
-
* Informs users about the aggregation level
|
|
48
|
+
* Informs users about the aggregation level due to high data volume.
|
|
11
49
|
*/
|
|
12
50
|
export function AggregatedDataBanner({
|
|
13
|
-
|
|
14
|
-
|
|
51
|
+
bucketIntervalSeconds,
|
|
52
|
+
checkIntervalSeconds,
|
|
15
53
|
}: AggregatedDataBannerProps) {
|
|
16
|
-
|
|
54
|
+
// Only show if bucket interval is larger than check interval (data is being aggregated)
|
|
55
|
+
if (checkIntervalSeconds && bucketIntervalSeconds <= checkIntervalSeconds) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const tier = getResolutionTier(bucketIntervalSeconds);
|
|
17
60
|
|
|
18
61
|
return (
|
|
19
62
|
<InfoBanner variant="info">
|
|
20
|
-
|
|
21
|
-
{
|
|
63
|
+
{TIER_LABELS[tier]} • Data aggregated into{" "}
|
|
64
|
+
{formatDuration(bucketIntervalSeconds)} intervals
|
|
22
65
|
</InfoBanner>
|
|
23
66
|
);
|
|
24
67
|
}
|
|
@@ -11,8 +11,10 @@ interface HealthCheckDiagramProps {
|
|
|
11
11
|
context: HealthCheckDiagramSlotContext;
|
|
12
12
|
/** Whether the data is aggregated (for showing the info banner) */
|
|
13
13
|
isAggregated?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/** The bucket interval in seconds (from aggregated response) */
|
|
15
|
+
bucketIntervalSeconds?: number;
|
|
16
|
+
/** The check interval in seconds (for comparison in banner) */
|
|
17
|
+
checkIntervalSeconds?: number;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -22,20 +24,15 @@ interface HealthCheckDiagramProps {
|
|
|
22
24
|
export function HealthCheckDiagram({
|
|
23
25
|
context,
|
|
24
26
|
isAggregated = false,
|
|
25
|
-
|
|
27
|
+
bucketIntervalSeconds,
|
|
28
|
+
checkIntervalSeconds,
|
|
26
29
|
}: HealthCheckDiagramProps) {
|
|
27
|
-
// Determine bucket size from context for aggregated data info banner
|
|
28
|
-
const bucketSize =
|
|
29
|
-
context.type === "aggregated" && context.buckets.length > 0
|
|
30
|
-
? context.buckets[0].bucketSize
|
|
31
|
-
: "hourly";
|
|
32
|
-
|
|
33
30
|
return (
|
|
34
31
|
<>
|
|
35
|
-
{isAggregated && (
|
|
32
|
+
{isAggregated && bucketIntervalSeconds && (
|
|
36
33
|
<AggregatedDataBanner
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
bucketIntervalSeconds={bucketIntervalSeconds}
|
|
35
|
+
checkIntervalSeconds={checkIntervalSeconds}
|
|
39
36
|
/>
|
|
40
37
|
)}
|
|
41
38
|
<ExtensionSlot slot={HealthCheckDiagramSlot} context={context} />
|
|
@@ -40,13 +40,13 @@ export const HealthCheckEditor: React.FC<HealthCheckEditorProps> = ({
|
|
|
40
40
|
const [name, setName] = useState(initialData?.name || "");
|
|
41
41
|
const [strategyId, setStrategyId] = useState(initialData?.strategyId || "");
|
|
42
42
|
const [interval, setInterval] = useState(
|
|
43
|
-
initialData?.intervalSeconds?.toString() || "60"
|
|
43
|
+
initialData?.intervalSeconds?.toString() || "60",
|
|
44
44
|
);
|
|
45
45
|
const [config, setConfig] = useState<Record<string, unknown>>(
|
|
46
|
-
(initialData?.config as Record<string, unknown>) || {}
|
|
46
|
+
(initialData?.config as Record<string, unknown>) || {},
|
|
47
47
|
);
|
|
48
48
|
const [collectors, setCollectors] = useState<CollectorConfigEntry[]>(
|
|
49
|
-
initialData?.collectors || []
|
|
49
|
+
initialData?.collectors || [],
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
const toast = useToast();
|
|
@@ -111,7 +111,7 @@ export const HealthCheckEditor: React.FC<HealthCheckEditorProps> = ({
|
|
|
111
111
|
</DialogDescription>
|
|
112
112
|
</DialogHeader>
|
|
113
113
|
|
|
114
|
-
<div className="space-y-6 py-4
|
|
114
|
+
<div className="space-y-6 py-4">
|
|
115
115
|
<div className="space-y-2">
|
|
116
116
|
<Label htmlFor="name">Name</Label>
|
|
117
117
|
<Input
|
|
@@ -50,8 +50,11 @@ export const HealthCheckLatencyChart: React.FC<
|
|
|
50
50
|
? chartData.reduce((sum, d) => sum + d.latencyMs, 0) / chartData.length
|
|
51
51
|
: 0;
|
|
52
52
|
|
|
53
|
+
// Use daily format for intervals >= 6 hours, otherwise include time
|
|
53
54
|
const timeFormat =
|
|
54
|
-
buckets[0]?.
|
|
55
|
+
(buckets[0]?.bucketIntervalSeconds ?? 3600) >= 21_600
|
|
56
|
+
? "MMM d"
|
|
57
|
+
: "MMM d HH:mm";
|
|
55
58
|
|
|
56
59
|
return (
|
|
57
60
|
<ResponsiveContainer width="100%" height={height}>
|
|
@@ -73,7 +76,7 @@ export const HealthCheckLatencyChart: React.FC<
|
|
|
73
76
|
<XAxis
|
|
74
77
|
dataKey="timestamp"
|
|
75
78
|
type="number"
|
|
76
|
-
domain={["
|
|
79
|
+
domain={["dataMin", "dataMax"]}
|
|
77
80
|
tickFormatter={(ts: number) => format(new Date(ts), timeFormat)}
|
|
78
81
|
stroke="hsl(var(--muted-foreground))"
|
|
79
82
|
fontSize={12}
|
|
@@ -172,7 +175,7 @@ export const HealthCheckLatencyChart: React.FC<
|
|
|
172
175
|
<XAxis
|
|
173
176
|
dataKey="timestamp"
|
|
174
177
|
type="number"
|
|
175
|
-
domain={["
|
|
178
|
+
domain={["dataMin", "dataMax"]}
|
|
176
179
|
tickFormatter={(ts: number) => format(new Date(ts), "HH:mm")}
|
|
177
180
|
stroke="hsl(var(--muted-foreground))"
|
|
178
181
|
fontSize={12}
|
|
@@ -50,8 +50,11 @@ export const HealthCheckStatusTimeline: React.FC<
|
|
|
50
50
|
total: d.runCount,
|
|
51
51
|
}));
|
|
52
52
|
|
|
53
|
+
// Use daily format for intervals >= 6 hours, otherwise include time
|
|
53
54
|
const timeFormat =
|
|
54
|
-
buckets[0]?.
|
|
55
|
+
(buckets[0]?.bucketIntervalSeconds ?? 3600) >= 21_600
|
|
56
|
+
? "MMM d"
|
|
57
|
+
: "MMM d HH:mm";
|
|
55
58
|
|
|
56
59
|
return (
|
|
57
60
|
<ResponsiveContainer width="100%" height={height}>
|
|
@@ -59,7 +62,7 @@ export const HealthCheckStatusTimeline: React.FC<
|
|
|
59
62
|
<XAxis
|
|
60
63
|
dataKey="timestamp"
|
|
61
64
|
type="number"
|
|
62
|
-
domain={["
|
|
65
|
+
domain={["dataMin", "dataMax"]}
|
|
63
66
|
tickFormatter={(ts: number) => format(new Date(ts), timeFormat)}
|
|
64
67
|
stroke="hsl(var(--muted-foreground))"
|
|
65
68
|
fontSize={10}
|
|
@@ -133,7 +136,7 @@ export const HealthCheckStatusTimeline: React.FC<
|
|
|
133
136
|
<XAxis
|
|
134
137
|
dataKey="timestamp"
|
|
135
138
|
type="number"
|
|
136
|
-
domain={["
|
|
139
|
+
domain={["dataMin", "dataMax"]}
|
|
137
140
|
tickFormatter={(ts: number) => format(new Date(ts), "HH:mm")}
|
|
138
141
|
stroke="hsl(var(--muted-foreground))"
|
|
139
142
|
fontSize={10}
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
usePagination,
|
|
21
21
|
usePaginationSync,
|
|
22
22
|
DateRangeFilter,
|
|
23
|
+
getPresetRange,
|
|
24
|
+
DateRangePreset,
|
|
23
25
|
} from "@checkstack/ui";
|
|
24
26
|
import { formatDistanceToNow } from "date-fns";
|
|
25
27
|
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
@@ -55,17 +57,10 @@ interface ExpandedRowProps {
|
|
|
55
57
|
const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
|
|
56
58
|
const healthCheckClient = usePluginClient(HealthCheckApi);
|
|
57
59
|
|
|
58
|
-
// Date range state for filtering
|
|
59
|
-
const [dateRange, setDateRange] = useState
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}>(() => {
|
|
63
|
-
// Default to last 24 hours
|
|
64
|
-
const end = new Date();
|
|
65
|
-
const start = new Date();
|
|
66
|
-
start.setHours(start.getHours() - 24);
|
|
67
|
-
return { startDate: start, endDate: end };
|
|
68
|
-
});
|
|
60
|
+
// Date range state for filtering - default to last 24 hours
|
|
61
|
+
const [dateRange, setDateRange] = useState(() =>
|
|
62
|
+
getPresetRange(DateRangePreset.Last24Hours),
|
|
63
|
+
);
|
|
69
64
|
|
|
70
65
|
// Use shared hook for chart data - handles both raw and aggregated modes
|
|
71
66
|
// and includes signal handling for automatic refresh
|
|
@@ -73,7 +68,7 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
|
|
|
73
68
|
context: chartContext,
|
|
74
69
|
loading: chartLoading,
|
|
75
70
|
isAggregated,
|
|
76
|
-
|
|
71
|
+
bucketIntervalSeconds,
|
|
77
72
|
} = useHealthCheckData({
|
|
78
73
|
systemId,
|
|
79
74
|
configurationId: item.configurationId,
|
|
@@ -160,7 +155,8 @@ const ExpandedDetails: React.FC<ExpandedRowProps> = ({ item, systemId }) => {
|
|
|
160
155
|
<HealthCheckDiagram
|
|
161
156
|
context={chartContext}
|
|
162
157
|
isAggregated={isAggregated}
|
|
163
|
-
|
|
158
|
+
bucketIntervalSeconds={bucketIntervalSeconds}
|
|
159
|
+
checkIntervalSeconds={item.intervalSeconds}
|
|
164
160
|
/>
|
|
165
161
|
</div>
|
|
166
162
|
);
|
|
@@ -281,8 +277,8 @@ export function HealthCheckSystemOverview(props: SlotProps) {
|
|
|
281
277
|
void refetch();
|
|
282
278
|
}
|
|
283
279
|
},
|
|
284
|
-
[systemId, refetch]
|
|
285
|
-
)
|
|
280
|
+
[systemId, refetch],
|
|
281
|
+
),
|
|
286
282
|
);
|
|
287
283
|
|
|
288
284
|
if (initialLoading) {
|
|
@@ -44,6 +44,8 @@ interface UseHealthCheckDataResult {
|
|
|
44
44
|
hasAccess: boolean;
|
|
45
45
|
/** Whether access is still loading */
|
|
46
46
|
accessLoading: boolean;
|
|
47
|
+
/** Bucket interval in seconds (only for aggregated mode) */
|
|
48
|
+
bucketIntervalSeconds: number | undefined;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/**
|
|
@@ -67,14 +69,14 @@ export function useHealthCheckData({
|
|
|
67
69
|
|
|
68
70
|
// Access state
|
|
69
71
|
const { allowed: hasAccess, loading: accessLoading } = accessApi.useAccess(
|
|
70
|
-
healthCheckAccess.details
|
|
72
|
+
healthCheckAccess.details,
|
|
71
73
|
);
|
|
72
74
|
|
|
73
75
|
// Calculate date range in days
|
|
74
76
|
const dateRangeDays = useMemo(() => {
|
|
75
77
|
return Math.ceil(
|
|
76
78
|
(dateRange.endDate.getTime() - dateRange.startDate.getTime()) /
|
|
77
|
-
(1000 * 60 * 60 * 24)
|
|
79
|
+
(1000 * 60 * 60 * 24),
|
|
78
80
|
);
|
|
79
81
|
}, [dateRange.startDate, dateRange.endDate]);
|
|
80
82
|
|
|
@@ -82,7 +84,7 @@ export function useHealthCheckData({
|
|
|
82
84
|
const { data: retentionData, isLoading: retentionLoading } =
|
|
83
85
|
healthCheckClient.getRetentionConfig.useQuery(
|
|
84
86
|
{ systemId, configurationId },
|
|
85
|
-
{ enabled: !!systemId && !!configurationId && hasAccess }
|
|
87
|
+
{ enabled: !!systemId && !!configurationId && hasAccess },
|
|
86
88
|
);
|
|
87
89
|
|
|
88
90
|
const retentionConfig =
|
|
@@ -101,6 +103,7 @@ export function useHealthCheckData({
|
|
|
101
103
|
systemId,
|
|
102
104
|
configurationId,
|
|
103
105
|
startDate: dateRange.startDate,
|
|
106
|
+
endDate: dateRange.endDate,
|
|
104
107
|
limit,
|
|
105
108
|
offset,
|
|
106
109
|
},
|
|
@@ -112,11 +115,10 @@ export function useHealthCheckData({
|
|
|
112
115
|
!accessLoading &&
|
|
113
116
|
!retentionLoading &&
|
|
114
117
|
!isAggregated,
|
|
115
|
-
}
|
|
118
|
+
},
|
|
116
119
|
);
|
|
117
120
|
|
|
118
121
|
// Query: Fetch aggregated data (when in aggregated mode)
|
|
119
|
-
const bucketSize = dateRangeDays > 30 ? "daily" : "hourly";
|
|
120
122
|
const { data: aggregatedData, isLoading: aggregatedLoading } =
|
|
121
123
|
healthCheckClient.getDetailedAggregatedHistory.useQuery(
|
|
122
124
|
{
|
|
@@ -124,7 +126,7 @@ export function useHealthCheckData({
|
|
|
124
126
|
configurationId,
|
|
125
127
|
startDate: dateRange.startDate,
|
|
126
128
|
endDate: dateRange.endDate,
|
|
127
|
-
|
|
129
|
+
targetPoints: 500,
|
|
128
130
|
},
|
|
129
131
|
{
|
|
130
132
|
enabled:
|
|
@@ -134,7 +136,7 @@ export function useHealthCheckData({
|
|
|
134
136
|
!accessLoading &&
|
|
135
137
|
!retentionLoading &&
|
|
136
138
|
isAggregated,
|
|
137
|
-
}
|
|
139
|
+
},
|
|
138
140
|
);
|
|
139
141
|
|
|
140
142
|
// Listen for realtime health check updates to refresh data silently
|
|
@@ -223,5 +225,6 @@ export function useHealthCheckData({
|
|
|
223
225
|
retentionConfig,
|
|
224
226
|
hasAccess,
|
|
225
227
|
accessLoading,
|
|
228
|
+
bucketIntervalSeconds: aggregatedData?.bucketIntervalSeconds,
|
|
226
229
|
};
|
|
227
230
|
}
|