From 98ce7c220455fa6a00b7a2fb25246c4a4e6efa80 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 12 Apr 2026 21:40:43 +0200 Subject: [PATCH] feat: combine process diagram and processor table into toggled card Dashboard L3 now shows a single Processor Metrics card with Diagram/Table toggle buttons. The diagram shows native tooltips on hover with full processor metrics (avg, p99, invocations, error rate, % time). Also fixes: - Chart x-axis uses actual timestamps instead of bucket indices - formatDurationShort uses locale formatting with max 3 decimals Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/ProcessDiagram/DiagramNode.tsx | 12 ++++ ui/src/components/ProcessDiagram/types.ts | 4 ++ ui/src/pages/DashboardTab/DashboardL3.tsx | 68 ++++++++++++------- ui/src/utils/format-utils.ts | 4 +- 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/ui/src/components/ProcessDiagram/DiagramNode.tsx b/ui/src/components/ProcessDiagram/DiagramNode.tsx index 7539677f..bc8d6ece 100644 --- a/ui/src/components/ProcessDiagram/DiagramNode.tsx +++ b/ui/src/components/ProcessDiagram/DiagramNode.tsx @@ -102,6 +102,18 @@ export function DiagramNode({ style={{ cursor: 'pointer' }} opacity={isSkipped ? 0.35 : undefined} > + {/* Processor metrics tooltip */} + {heatmapEntry && ( + {[ + `${node.id}${heatmapEntry.processorType ? ` (${heatmapEntry.processorType})` : ''}`, + `Avg: ${heatmapEntry.avgDurationMs.toLocaleString(undefined, { maximumFractionDigits: 3 })}ms`, + `P99: ${heatmapEntry.p99DurationMs.toLocaleString(undefined, { maximumFractionDigits: 3 })}ms`, + `Time: ${heatmapEntry.pctOfRoute.toFixed(1)}%`, + heatmapEntry.totalCount != null ? `Invocations: ${heatmapEntry.totalCount.toLocaleString()}` : '', + heatmapEntry.errorRate != null ? `Errors: ${(heatmapEntry.errorRate * 100).toFixed(2)}%` : '', + ].filter(Boolean).join('\n')} + )} + {/* Selection ring */} {isSelected && ( ('diagram'); const { appId, routeId } = useParams<{ appId: string; routeId: string }>(); const selectedEnv = useEnvironmentStore((s) => s.environment); const { timeRange } = useGlobalFilters(); @@ -290,8 +291,8 @@ export default function DashboardL3() { // ── Chart data ─────────────────────────────────────────────────────────── const chartData = useMemo(() => - (timeseries?.buckets || []).map((b: any, i: number) => ({ - idx: i, + (timeseries?.buckets || []).map((b: any) => ({ + time: b.time, throughput: b.totalCount, p99: b.p99DurationMs, errorRate: b.totalCount > 0 ? (b.failedCount / b.totalCount) * 100 : 0, @@ -299,6 +300,9 @@ export default function DashboardL3() { [timeseries], ); + const formatTime = (t: string) => + new Date(t).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + // ── Processor table rows ──────────────────────────────────────────────── const processorRows: ProcessorRow[] = useMemo(() => { if (!processorMetrics?.length) return []; @@ -321,12 +325,15 @@ export default function DashboardL3() { const totalAvg = processorMetrics.reduce( (sum: number, m: any) => sum + m.avgDurationMs, 0, ); - const map = new Map(); + const map = new Map(); for (const m of processorMetrics) { map.set(m.processorId, { avgDurationMs: m.avgDurationMs, p99DurationMs: m.p99DurationMs, pctOfRoute: totalAvg > 0 ? (m.avgDurationMs / totalAvg) * 100 : 0, + processorType: m.processorType, + totalCount: m.totalCount, + errorRate: m.errorRate, }); } return map; @@ -352,14 +359,14 @@ export default function DashboardL3() { {(timeseries?.buckets?.length ?? 0) > 0 && (
- + - + @@ -367,7 +374,7 @@ export default function DashboardL3() { - + @@ -375,33 +382,42 @@ export default function DashboardL3() {
)} - {/* Process Diagram with Latency Heatmap */} - {appId && routeId && ( -
- -
- )} - - {/* Processor Metrics Table */} + {/* Processor Metrics — toggle between diagram and table */}
Processor Metrics -
+
{processorRows.length} processor{processorRows.length !== 1 ? 's' : ''} +
+ + +
- + {processorView === 'diagram' && appId && routeId ? ( +
+ +
+ ) : ( + + )}
{/* Top 5 Errors — hidden if empty */} diff --git a/ui/src/utils/format-utils.ts b/ui/src/utils/format-utils.ts index d05d5172..8e7a5a84 100644 --- a/ui/src/utils/format-utils.ts +++ b/ui/src/utils/format-utils.ts @@ -15,8 +15,8 @@ export function formatDurationShort(ms: number | undefined): string { const seconds = Math.round((ms % 60_000) / 1000); return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`; } - if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`; - return `${ms}ms`; + if (ms >= 1000) return `${(ms / 1000).toLocaleString(undefined, { maximumFractionDigits: 1 })}s`; + return `${ms.toLocaleString(undefined, { maximumFractionDigits: 3 })}ms`; } export function statusLabel(s: string): string {