Skip to content

Health Check Custom Charts

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:

  1. Generic Charts (Platform-provided)

    • HealthCheckLatencyChart: Tracks execution speed trends
    • HealthCheckStatusTimeline: Visualizes success/failure over time
    • Work with all health checks, regardless of strategy
  2. Strategy-Specific Diagrams (Custom)

    • Injected via HealthCheckDiagramSlot
    • Filtered to only show for relevant strategies
    • Have access to full result/aggregatedResult data

Custom charts must handle two data modes based on the selected time range:

ModeContext TypeDataWhen Used
RawRawDiagramContextIndividual run resultsShort ranges (≤ rawRetentionDays)
AggregatedAggregatedDiagramContextBucketed summariesLong 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>;

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.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],
});
function 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.”

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!
}

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} />;
}
}

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 AggregatedDataBanner when in aggregated mode
  • Renders the HealthCheckDiagramSlot with proper context
LibraryBest ForNotes
RechartsMost use casesComponent-based, good DX, SVG rendering
NivoHighly responsive chartsBuilt-in theming, Canvas support
react-chartjs-2Performance-criticalCanvas-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}`}
/>