The health check platform supports strategy-specific visualizations through an extension slot system. This allows health check strategies to provide specialized charts for their unique data (e.g., HTTP status code distribution, database connection pool stats).
The platform provides two types of charts:
HealthCheckLatencyChart: Tracks execution speed trendsHealthCheckStatusTimeline: Visualizes success/failure over timeHealthCheckDiagramSlotresult/aggregatedResult dataCustom charts must handle two data modes based on the selected time range:
| Mode | Context Type | Data | When Used |
|---|---|---|---|
| Raw | RawDiagramContext |
Individual run results | Short ranges (≤ rawRetentionDays) |
| Aggregated | AggregatedDiagramContext |
Bucketed summaries | Long ranges (> rawRetentionDays) |
In your strategy’s common package, define the result types:
// @checkstack/healthcheck-http-common/src/types.ts
import { z } from "zod";
// Per-run result schema
export const HttpResultSchema = z.object({
statusCode: z.number(),
responseTimeMs: z.number(),
contentLength: z.number().optional(),
});
export type HttpResult = z.infer<typeof HttpResultSchema>;
// Aggregated result schema
export const HttpAggregatedResultSchema = z.object({
statusCodeDistribution: z.record(z.string(), z.number()),
avgResponseTimeMs: z.number(),
errorRate: z.number(),
});
export type HttpAggregatedResult = z.infer<typeof HttpAggregatedResultSchema>;
In your strategy’s common package, create a typed helper:
// @checkstack/healthcheck-http-common/src/slots.ts
import { createDiagramExtensionFactory } from "@checkstack/healthcheck-frontend";
import { httpCheckMetadata } from "./plugin-metadata";
import type { HttpResult, HttpAggregatedResult } from "./types";
/**
* Pre-typed helper for creating HTTP check diagram extensions.
* Consumers get automatic type inference for their components.
*/
export const createHttpDiagramExtension = createDiagramExtensionFactory<
HttpResult,
HttpAggregatedResult
>(httpCheckMetadata);
In your strategy’s frontend package, create the chart components:
// @checkstack/healthcheck-http-frontend/src/charts/HttpStatusChart.tsx
import React from "react";
import { BarChart, Bar, XAxis, YAxis, Tooltip } from "recharts";
import type { RawDiagramContext } from "@checkstack/healthcheck-frontend";
import type { HttpResult } from "@checkstack/healthcheck-http-common";
/**
* Raw mode component - renders per-run data.
*/
export const HttpStatusRawChart: React.FC<RawDiagramContext<HttpResult>> = ({
runs,
}) => {
// Aggregate status codes from individual runs
const statusCounts: Record<string, number> = {};
for (const run of runs) {
const code = String(run.result?.statusCode ?? "unknown");
statusCounts[code] = (statusCounts[code] ?? 0) + 1;
}
const data = Object.entries(statusCounts).map(([code, count]) => ({
code,
count,
}));
return (
<div className="p-4">
<h4 className="text-sm font-medium mb-2">Status Code Distribution</h4>
<BarChart width={400} height={200} data={data}>
<XAxis dataKey="code" />
<YAxis />
<Tooltip />
<Bar dataKey="count" fill="#3b82f6" />
</BarChart>
</div>
);
};
// @checkstack/healthcheck-http-frontend/src/charts/HttpStatusAggregatedChart.tsx
import React from "react";
import { BarChart, Bar, XAxis, YAxis, Tooltip } from "recharts";
import type { AggregatedDiagramContext } from "@checkstack/healthcheck-frontend";
import type { HttpAggregatedResult } from "@checkstack/healthcheck-http-common";
/**
* Aggregated mode component - renders bucketed data.
*/
export const HttpStatusAggregatedChart: React.FC<
AggregatedDiagramContext<HttpAggregatedResult>
> = ({ buckets }) => {
// Combine status distributions across all buckets
const totalDistribution: Record<string, number> = {};
for (const bucket of buckets) {
const dist = bucket.aggregatedResult?.statusCodeDistribution ?? {};
for (const [code, count] of Object.entries(dist)) {
totalDistribution[code] = (totalDistribution[code] ?? 0) + count;
}
}
const data = Object.entries(totalDistribution).map(([code, count]) => ({
code,
count,
}));
return (
<div className="p-4">
<h4 className="text-sm font-medium mb-2">Status Code Distribution (Aggregated)</h4>
<BarChart width={400} height={200} data={data}>
<XAxis dataKey="code" />
<YAxis />
<Tooltip />
<Bar dataKey="count" fill="#8b5cf6" />
</BarChart>
</div>
);
};
In your strategy’s frontend plugin, register the extension:
// @checkstack/healthcheck-http-frontend/src/index.tsx
import { createFrontendPlugin } from "@checkstack/frontend-api";
import { createHttpDiagramExtension } from "@checkstack/healthcheck-http-common";
import { HttpStatusRawChart } from "./charts/HttpStatusChart";
import { HttpStatusAggregatedChart } from "./charts/HttpStatusAggregatedChart";
// Create the extension using the typed helper
const httpStatusDiagram = createHttpDiagramExtension({
id: "http-check.status-distribution",
rawComponent: HttpStatusRawChart, // Required
aggregatedComponent: HttpStatusAggregatedChart, // Optional
});
export default createFrontendPlugin({
name: "healthcheck-http-frontend",
extensions: [httpStatusDiagram],
});
createDiagramExtensionFactory APIfunction createDiagramExtensionFactory<TResult, TAggregatedResult>(
strategyMetadata: PluginMetadata
): (options: {
id: string;
rawComponent: React.ComponentType<RawDiagramContext<TResult>>;
aggregatedComponent?: React.ComponentType<AggregatedDiagramContext<TAggregatedResult>>;
}) => SlotExtension;
strategyMetadata: Your strategy’s plugin metadata. Used for filtering.id: Unique extension ID (e.g., "http-check.status-chart")rawComponent: Component for rendering individual run data (required)aggregatedComponent: Component for rendering aggregated bucket data (optional)If aggregatedComponent is not provided, the platform shows a fallback message:
“Strategy does not support aggregated visualization. Select a shorter time range for detailed per-run data.”
RawDiagramContext<TResult>interface RawDiagramContext<TResult> {
type: "raw";
systemId: string;
configurationId: string;
strategyId: string;
runs: TypedHealthCheckRun<TResult>[];
}
interface TypedHealthCheckRun<TResult> {
id: string;
configurationId: string;
systemId: string;
status: "healthy" | "unhealthy" | "degraded";
timestamp: Date;
latencyMs?: number;
result: TResult; // Typed!
}
AggregatedDiagramContext<TAggregatedResult>interface AggregatedDiagramContext<TAggregatedResult> {
type: "aggregated";
systemId: string;
configurationId: string;
strategyId: string;
buckets: TypedAggregatedBucket<TAggregatedResult>[];
}
interface TypedAggregatedBucket<TAggregatedResult> {
bucketStart: Date;
bucketSize: "hourly" | "daily";
runCount: number;
healthyCount: number;
degradedCount: number;
unhealthyCount: number;
successRate: number;
avgLatencyMs?: number;
minLatencyMs?: number;
maxLatencyMs?: number;
p95LatencyMs?: number;
aggregatedResult?: TAggregatedResult; // Typed!
}
useHealthCheckData HookFor advanced use cases, you can access the raw hook directly:
import { useHealthCheckData } from "@checkstack/healthcheck-frontend";
function MyCustomVisualization({ systemId, configurationId, strategyId, dateRange }) {
const {
context, // Ready-to-use slot context
loading, // Loading state
isAggregated, // Whether aggregated mode is active
retentionConfig, // Current retention settings
hasAccess, // User has healthCheckDetailsRead
accessLoading,
} = useHealthCheckData({
systemId,
configurationId,
strategyId,
dateRange,
limit: 100, // For raw mode pagination
offset: 0,
});
if (loading) return <LoadingSpinner />;
if (!hasAccess) return <NoAccessBanner />;
if (!context) return null;
// Render based on context.type
if (context.type === "raw") {
return <MyRawChart runs={context.runs} />;
} else {
return <MyAggregatedChart buckets={context.buckets} />;
}
}
HealthCheckDiagram ComponentFor the simplest integration, use the wrapper component:
import { HealthCheckDiagram } from "@checkstack/healthcheck-frontend";
function MyPage({ systemId, configurationId, strategyId, dateRange }) {
return (
<HealthCheckDiagram
systemId={systemId}
configurationId={configurationId}
strategyId={strategyId}
dateRange={dateRange}
/>
);
}
This component:
AggregatedDataBanner when in aggregated modeHealthCheckDiagramSlot with proper context| Library | Best For | Notes |
|---|---|---|
| Recharts | Most use cases | Component-based, good DX, SVG rendering |
| Nivo | Highly responsive charts | Built-in theming, Canvas support |
| react-chartjs-2 | Performance-critical | Canvas-based, Chart.js wrapper |
// ✅ Good - graceful handling
const data = bucket.aggregatedResult?.statusCodeDistribution ?? {};
// ❌ Bad - may crash
const data = bucket.aggregatedResult.statusCodeDistribution;
// ✅ Good - memoize computed data
const chartData = useMemo(() => {
return processRuns(runs);
}, [runs]);
// ❌ Bad - recalculates on every render
const chartData = processRuns(runs);
Follow the platform’s design system for colors and spacing:
// Use theme-aware colors
<Bar dataKey="count" fill="var(--color-primary)" />
// Use consistent padding
<div className="p-4">
<ChartComponent />
</div>
<Tooltip
formatter={(value: number) => [`${value} requests`, "Count"]}
labelFormatter={(label) => `Status: ${label}`}
/>