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) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import {
|
||||
KpiStrip,
|
||||
@@ -247,6 +247,7 @@ function buildKpiItems(
|
||||
// ── Component ───────────────────────────────────────────────────────────────
|
||||
|
||||
export default function DashboardL3() {
|
||||
const [processorView, setProcessorView] = useState<'diagram' | 'table'>('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<string, { avgDurationMs: number; p99DurationMs: number; pctOfRoute: number }>();
|
||||
const map = new Map<string, { avgDurationMs: number; p99DurationMs: number; pctOfRoute: number; processorType?: string; totalCount?: number; errorRate?: number }>();
|
||||
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 && (
|
||||
<div className={styles.chartRow}>
|
||||
<Card title="Throughput">
|
||||
<ThemedChart data={chartData} height={200} xDataKey="idx" yLabel="msg/s">
|
||||
<ThemedChart data={chartData} height={200} xDataKey="time" xTickFormatter={formatTime} yLabel="msg/s">
|
||||
<Area dataKey="throughput" name="Throughput" stroke={CHART_COLORS[0]}
|
||||
fill={CHART_COLORS[0]} fillOpacity={0.1} strokeWidth={2} dot={false} />
|
||||
</ThemedChart>
|
||||
</Card>
|
||||
|
||||
<Card title="Latency Percentiles">
|
||||
<ThemedChart data={chartData} height={200} xDataKey="idx" yLabel="ms">
|
||||
<ThemedChart data={chartData} height={200} xDataKey="time" xTickFormatter={formatTime} yLabel="ms">
|
||||
<Line dataKey="p99" name="P99" stroke={CHART_COLORS[0]} strokeWidth={2} dot={false} />
|
||||
<ReferenceLine y={slaThresholdMs} stroke="var(--error)" strokeDasharray="5 3"
|
||||
label={{ value: `SLA ${slaThresholdMs}ms`, position: 'right', fill: 'var(--error)', fontSize: 9 }} />
|
||||
@@ -367,7 +374,7 @@ export default function DashboardL3() {
|
||||
</Card>
|
||||
|
||||
<Card title="Error Rate">
|
||||
<ThemedChart data={chartData} height={200} xDataKey="idx" yLabel="%">
|
||||
<ThemedChart data={chartData} height={200} xDataKey="time" xTickFormatter={formatTime} yLabel="%">
|
||||
<Area dataKey="errorRate" name="Error Rate" stroke={CHART_COLORS[1]}
|
||||
fill={CHART_COLORS[1]} fillOpacity={0.1} strokeWidth={2} dot={false} />
|
||||
</ThemedChart>
|
||||
@@ -375,33 +382,42 @@ export default function DashboardL3() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Process Diagram with Latency Heatmap */}
|
||||
{appId && routeId && (
|
||||
<div className={`${tableStyles.tableSection} ${styles.diagramHeight}`}>
|
||||
<ProcessDiagram
|
||||
application={appId}
|
||||
routeId={routeId}
|
||||
diagramLayout={diagramLayout}
|
||||
latencyHeatmap={latencyHeatmap}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Processor Metrics Table */}
|
||||
{/* Processor Metrics — toggle between diagram and table */}
|
||||
<div className={tableStyles.tableSection}>
|
||||
<div className={tableStyles.tableHeader}>
|
||||
<span className={tableStyles.tableTitle}>Processor Metrics</span>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span className={tableStyles.tableMeta}>
|
||||
{processorRows.length} processor{processorRows.length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
<div className={styles.toggleRow}>
|
||||
<button
|
||||
className={`${styles.toggleBtn} ${processorView === 'diagram' ? styles.toggleActive : ''}`}
|
||||
onClick={() => setProcessorView('diagram')}
|
||||
>Diagram</button>
|
||||
<button
|
||||
className={`${styles.toggleBtn} ${processorView === 'table' ? styles.toggleActive : ''}`}
|
||||
onClick={() => setProcessorView('table')}
|
||||
>Table</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable
|
||||
columns={PROCESSOR_COLUMNS}
|
||||
data={processorRows}
|
||||
sortable
|
||||
/>
|
||||
{processorView === 'diagram' && appId && routeId ? (
|
||||
<div className={styles.diagramHeight}>
|
||||
<ProcessDiagram
|
||||
application={appId}
|
||||
routeId={routeId}
|
||||
diagramLayout={diagramLayout}
|
||||
latencyHeatmap={latencyHeatmap}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<DataTable
|
||||
columns={PROCESSOR_COLUMNS}
|
||||
data={processorRows}
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Top 5 Errors — hidden if empty */}
|
||||
|
||||
Reference in New Issue
Block a user