feat: add treemap and punchcard heatmap to dashboard L1/L2 (#94)

Treemap: rectangle area = transaction volume, color = SLA compliance
(green→red). Shows apps at L1, routes at L2. Click navigates deeper.

Punchcard heatmap: 7-day rolling weekday x 24-hour grid showing
transaction volume and error patterns. Two side-by-side views
(transactions + errors) reveal temporal clustering.

Backend: new GET /search/stats/punchcard endpoint aggregating
stats_1m_all/app by DOW x hour over rolling 7 days.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-30 10:26:26 +02:00
parent b5c19b6774
commit 9d2d87e7e1
9 changed files with 433 additions and 17 deletions

View File

@@ -15,8 +15,11 @@ import type { KpiItem, Column } from '@cameleer/design-system';
import { useGlobalFilters } from '@cameleer/design-system';
import { useRouteMetrics } from '../../api/queries/catalog';
import { useExecutionStats, useStatsTimeseries } from '../../api/queries/executions';
import { useTimeseriesByApp, useTopErrors, useAllAppSettings } from '../../api/queries/dashboard';
import { useTimeseriesByApp, useTopErrors, useAllAppSettings, usePunchcard } from '../../api/queries/dashboard';
import type { AppSettings } from '../../api/queries/dashboard';
import { Treemap } from './Treemap';
import type { TreemapItem } from './Treemap';
import { PunchcardHeatmap } from './PunchcardHeatmap';
import type { RouteMetrics } from '../../api/types';
import {
computeHealthDot,
@@ -294,6 +297,7 @@ export default function DashboardL1() {
const { data: timeseries } = useStatsTimeseries(timeFrom, timeTo);
const { data: timeseriesByApp } = useTimeseriesByApp(timeFrom, timeTo);
const { data: topErrors } = useTopErrors(timeFrom, timeTo);
const { data: punchcardData } = usePunchcard();
const { data: allAppSettings } = useAllAppSettings();
// Build settings lookup map
@@ -387,6 +391,12 @@ export default function DashboardL1() {
}));
}, [timeseriesByApp]);
// Treemap items: one per app, sized by exchange count, colored by SLA
const treemapItems: TreemapItem[] = useMemo(
() => appRows.map(r => ({ id: r.appId, label: r.appId, value: r.throughput, slaCompliance: r.slaCompliance })),
[appRows],
);
return (
<div className={styles.content}>
<div className={styles.refreshIndicator}>
@@ -437,6 +447,30 @@ export default function DashboardL1() {
</Card>
</div>
)}
{/* Treemap: app volume vs SLA compliance */}
{treemapItems.length > 0 && (
<Card title="Application Volume vs SLA Compliance">
<Treemap
items={treemapItems}
width={800}
height={250}
onItemClick={(id) => navigate(`/dashboard/${id}`)}
/>
</Card>
)}
{/* Punchcard heatmaps: transactions and errors by weekday x hour */}
{(punchcardData?.length ?? 0) > 0 && (
<div className={styles.chartGrid}>
<Card title="Transaction Volume (7-day pattern)">
<PunchcardHeatmap cells={punchcardData!} mode="transactions" width={380} height={300} />
</Card>
<Card title="Error Volume (7-day pattern)">
<PunchcardHeatmap cells={punchcardData!} mode="errors" width={380} height={300} />
</Card>
</div>
)}
</div>
);
}