Observability
Delivery Heatmap
Webhook attempts by endpoint and hour — volume as bar height, failing hours picked out, with a dot-column total on top.
1 variantMIT
Preview
Webhooks: Deliveries
Attempts by endpoint
Nov 4 · UTC
orders-api
billing-svc
crm-sync
slack-relay
analytics
fulfil-eu
search-index
audit-log
FewerMore
Failing hour
- Attempts
- 28,825
- Delivered
- 28,721
- Failed
- 104
Attempts by endpoint, Nov 4 · UTC: 8 endpoints over 24 columns, 28,825 attempts, 104 failed. Failing: slack-relay at 19:00, audit-log at 03:00.
Installation
Copies the component into components/ui and installs its dependencies and theme tokens.
pnpm dlx shadcn@latest add https://tuffui.com/r/delivery-heatmap.jsonUsage
Pass your own data and wire the callbacks to your agent runtime.
import { DeliveryHeatmap, type DeliveryRange } from "@/components/ui/delivery-heatmap";
export function WebhookDeliveries({ day }: { day: string }) {
const [range, setRange] = useState<DeliveryRange>("24h");
// One row per endpoint, one bucket per 5 min / hour / day.
const { endpoints, labels } = useDeliveryStats(range);
return (
<DeliveryHeatmap
range={range}
onRangeChange={setRange}
date={`${day} · UTC`}
labels={labels} // ["00:00", "01:00", …]
endpoints={endpoints.map((e) => ({
id: e.id,
label: e.name, // "billing-svc"
buckets: e.buckets.map((b) => ({
attempts: b.attempts, // retries included
failed: b.failed,
status: b.topErrorStatus, // 503
})),
}))}
onSelect={(endpoint, i) => openAttempts(endpoint.id, labels[i])}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| endpoints* | DeliveryEndpoint[] | — | One row per endpoint, top to bottom. Each bucket fills its slot as high as its attempt volume, on five levels against the busiest bucket in view; a failing bucket turns red. |
| labels* | string[] | — | One label per column, e.g. `"11:00"` or `"Nov 4"`. Sets the column count and is used on the axis and in the tooltip. |
| ticks | number[] | even spread | Column indexes to label on the axis. Left out, labels thin out to fit the width. |
| range | "1h" | "24h" | "7d" | — | Controlled range. The component doesn't bucket anything itself: swap `endpoints` and `labels` when it changes, and the cells refill with a short stagger. |
| defaultRange | "1h" | "24h" | "7d" | "24h" | Starting range when uncontrolled. |
| onRangeChange | (range: DeliveryRange) => void | — | Shows the 1h / 24h / 7d switch. Called with the new range. |
| failThreshold | number | 0.05 | Share of failed attempts from which a bucket reads as failing. A bucket's own `failing` flag wins over it. |
| title | string | "Deliveries" | Header title. |
| section | string | "Webhooks" | Breadcrumb before the title. Pass an empty string to drop it. |
| subtitle | string | — | Secondary line under the title. |
| heading | string | "Attempts by endpoint" | Heading above the chart, also the grid's accessible name. |
| date | string | — | Window shown next to the heading, e.g. "Nov 4 · UTC". |
| stats | DeliveryStat[] | attempts, delivered, failed | Footer row. Numbers roll to their new value. Pass `[]` to hide it. |
| intro | boolean | true | Fills the cells in column by column on mount. Off renders the finished chart. Skipped with reduced motion. |
| formatCount | (n: number) => string | "61,204" | Formats counts in the tooltip, footer and cell labels. |
| onSelect | (endpoint: DeliveryEndpoint, bucketIndex: number) => void | — | Called when a cell is clicked, or picked with Enter. Cells take arrow keys, Home and End; hover and focus show the tooltip on their own. |
| className | string | — | Extra classes for the root element. |
type DeliveryRange = "1h" | "24h" | "7d";
type DeliveryEndpoint = {
id: string;
label: string; // "billing-svc"
buckets: DeliveryBucket[]; // same order as labels
};
type DeliveryBucket = {
attempts: number; // retries included
failed: number;
status?: number | string; // shown in the tooltip, e.g. 503
failing?: boolean; // overrides failThreshold
};
type DeliveryStat = {
label: string;
value: number | string;
tone?: "neutral" | "brand" | "negative";
};