Health Check Custom Charts
Overview
Section titled “Overview”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).
Architecture
Section titled “Architecture”Dual Visualization Pattern
Section titled “Dual Visualization Pattern”The platform provides two types of charts:
-
Generic Charts (Platform-provided)
HealthCheckLatencyChart: Tracks execution speed trendsHealthCheckStatusTimeline: Visualizes success/failure over time- Work with all health checks, regardless of strategy
-
Strategy-Specific Diagrams (Custom)
- Injected via
HealthCheckDiagramSlot - Filtered to only show for relevant strategies
- Have access to full
result/aggregatedResultdata
- Injected via
Data Modes
Section titled “Data Modes”Custom 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) |
Creating Custom Charts
Section titled “Creating Custom Charts”Step 1: Define Your Types
Section titled “Step 1: Define Your Types”In your strategy’s common package, define the result types:
// @checkstack/healthcheck-http-common/src/types.tsimport { z } from "zod";
// Per-run result schemaexport const HttpResultSchema = z.object({ statusCode: z.number(), responseTimeMs: z.number(), contentLength: z.number().optional(),});export type HttpResult = z.infer<typeof HttpResultSchema>;
// Aggregated result schemaexport const HttpAggregatedResultSchema = z.object({ statusCodeDistribution: z.record(z.string(), z.number()), avgResponseTimeMs: z.number(), errorRate: z.number(),});export type HttpAggregatedResult = z.infer<typeof HttpAggregatedResultSchema>;Step 2: Create the Diagram Extension Factory
Section titled “Step 2: Create the Diagram Extension Factory”In your strategy’s common package, create a typed helper:
// @checkstack/healthcheck-http-common/src/slots.tsimport { 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);Step 3: Implement Chart Components
Section titled “Step 3: Implement Chart Components”In your strategy’s frontend package, create the chart components:
// @checkstack/healthcheck-http-frontend/src/charts/HttpStatusChart.tsximport 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.tsximport 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> );};Step 4: Register the Extension
Section titled “Step 4: Register the Extension”In your strategy’s frontend plugin, register the extension:
// @checkstack/healthcheck-http-frontend/src/index.tsximport { 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 helperconst httpStatusDiagram = createHttpDiagramExtension({ id: "http-check.status-distribution", rawComponent: HttpStatusRawChart, // Required aggregatedComponent: HttpStatusAggregatedChart, // Optional});
export default createFrontendPlugin({ name: "healthcheck-http-frontend", extensions: [httpStatusDiagram],});The createDiagramExtensionFactory API
Section titled “The createDiagramExtensionFactory API”function createDiagramExtensionFactory<TResult, TAggregatedResult>( strategyMetadata: PluginMetadata): (options: { id: string; rawComponent: React.ComponentType<RawDiagramContext<TResult>>; aggregatedComponent?: React.ComponentType<AggregatedDiagramContext<TAggregatedResult>>;}) => SlotExtension;Parameters
Section titled “Parameters”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)
Automatic Fallback
Section titled “Automatic Fallback”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.”
Context Types
Section titled “Context Types”RawDiagramContext<TResult>
Section titled “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>
Section titled “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!}Using the useHealthCheckData Hook
Section titled “Using the useHealthCheckData Hook”For 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} />; }}Using the HealthCheckDiagram Component
Section titled “Using the HealthCheckDiagram Component”For 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:
- Handles loading states
- Shows access banners
- Displays
AggregatedDataBannerwhen in aggregated mode - Renders the
HealthCheckDiagramSlotwith proper context
Recommended Charting Libraries
Section titled “Recommended Charting Libraries”| 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 |
Best Practices
Section titled “Best Practices”1. Handle Missing Data
Section titled “1. Handle Missing Data”// ✅ Good - graceful handlingconst data = bucket.aggregatedResult?.statusCodeDistribution ?? {};
// ❌ Bad - may crashconst data = bucket.aggregatedResult.statusCodeDistribution;2. Keep Charts Performant
Section titled “2. Keep Charts Performant”// ✅ Good - memoize computed dataconst chartData = useMemo(() => { return processRuns(runs);}, [runs]);
// ❌ Bad - recalculates on every renderconst chartData = processRuns(runs);3. Use Consistent Styling
Section titled “3. Use Consistent Styling”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>4. Provide Meaningful Tooltips
Section titled “4. Provide Meaningful Tooltips”<Tooltip formatter={(value: number) => [`${value} requests`, "Count"]} labelFormatter={(label) => `Status: ${label}`}/>Next Steps
Section titled “Next Steps”- Health Check Data Management - Backend aggregation details
- Extension Points - General slot system
- Theming - Design tokens and colors