feat: migrate agent charts to ThemedChart + Recharts
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled

Replace custom LineChart/AreaChart/BarChart usage with ThemedChart
wrapper. Data format changed from ChartSeries[] to Recharts-native
flat objects. Uses DS v0.1.47.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-12 19:44:55 +02:00
parent a0af53f8f5
commit 0dae1f1cc7
6 changed files with 238 additions and 267 deletions

View File

@@ -3,8 +3,10 @@ import { useNavigate } from 'react-router';
import {
KpiStrip,
DataTable,
AreaChart,
LineChart,
ThemedChart,
Area,
Line,
CHART_COLORS,
Card,
Sparkline,
MonoText,
@@ -372,28 +374,39 @@ export default function DashboardL1() {
throughputSparkline, successSparkline, latencySparkline, slaSparkline, errorSparkline],
);
// ── Per-app chart series (throughput stacked area) ──────────────────────
const throughputByAppSeries = useMemo(() => {
if (!timeseriesByApp) return [];
return Object.entries(timeseriesByApp).map(([appId, { buckets }]) => ({
label: appId,
data: buckets.map((b, i) => ({
x: i as number,
y: b.totalCount,
})),
}));
// ── Per-app flat chart data (throughput stacked area) ───────────────────
const throughputByAppData = useMemo(() => {
if (!timeseriesByApp) return { data: [], keys: [] as string[] };
const entries = Object.entries(timeseriesByApp);
if (!entries.length) return { data: [], keys: [] as string[] };
const len = entries[0][1].buckets.length;
const keys = entries.map(([appId]) => appId);
const data = Array.from({ length: len }, (_, i) => {
const point: Record<string, number | string> = { idx: i };
for (const [appId, { buckets }] of entries) {
point[appId] = buckets[i]?.totalCount ?? 0;
}
return point;
});
return { data, keys };
}, [timeseriesByApp]);
// ── Per-app chart series (error rate line) ────────────────────────────
const errorRateByAppSeries = useMemo(() => {
if (!timeseriesByApp) return [];
return Object.entries(timeseriesByApp).map(([appId, { buckets }]) => ({
label: appId,
data: buckets.map((b, i) => ({
x: i as number,
y: b.totalCount > 0 ? (b.failedCount / b.totalCount) * 100 : 0,
})),
}));
// ── Per-app flat chart data (error rate line) ────────────────────────────
const errorRateByAppData = useMemo(() => {
if (!timeseriesByApp) return { data: [], keys: [] as string[] };
const entries = Object.entries(timeseriesByApp);
if (!entries.length) return { data: [], keys: [] as string[] };
const len = entries[0][1].buckets.length;
const keys = entries.map(([appId]) => appId);
const data = Array.from({ length: len }, (_, i) => {
const point: Record<string, number | string> = { idx: i };
for (const [appId, { buckets }] of entries) {
const b = buckets[i];
point[appId] = b && b.totalCount > 0 ? (b.failedCount / b.totalCount) * 100 : 0;
}
return point;
});
return { data, keys };
}, [timeseriesByApp]);
// Treemap items: one per app, sized by exchange count, colored by SLA
@@ -430,24 +443,24 @@ export default function DashboardL1() {
</div>
{/* Side-by-side charts */}
{throughputByAppSeries.length > 0 && (
{throughputByAppData.data.length > 0 && (
<div className={styles.chartGrid}>
<Card title="Throughput by Application (msg/s)">
<AreaChart
series={throughputByAppSeries}
yLabel="msg/s"
height={200}
className={styles.chart}
/>
<ThemedChart data={throughputByAppData.data} height={200} xDataKey="idx" yLabel="msg/s">
{throughputByAppData.keys.map((key, i) => (
<Area key={key} dataKey={key} name={key} stroke={CHART_COLORS[i % CHART_COLORS.length]}
fill={CHART_COLORS[i % CHART_COLORS.length]} fillOpacity={0.1} strokeWidth={2} dot={false} />
))}
</ThemedChart>
</Card>
<Card title="Error Rate by Application (%)">
<LineChart
series={errorRateByAppSeries}
yLabel="%"
height={200}
className={styles.chart}
/>
<ThemedChart data={errorRateByAppData.data} height={200} xDataKey="idx" yLabel="%">
{errorRateByAppData.keys.map((key, i) => (
<Line key={key} dataKey={key} name={key} stroke={CHART_COLORS[i % CHART_COLORS.length]}
strokeWidth={2} dot={false} />
))}
</ThemedChart>
</Card>
</div>
)}

View File

@@ -3,8 +3,11 @@ import { useParams, useNavigate } from 'react-router';
import {
KpiStrip,
DataTable,
AreaChart,
LineChart,
ThemedChart,
Area,
Line,
ReferenceLine,
CHART_COLORS,
Card,
Sparkline,
MonoText,
@@ -328,37 +331,31 @@ export default function DashboardL2() {
[stats, slaThresholdMs, throughputSparkline, latencySparkline, errors, windowSeconds],
);
// Throughput by Route — stacked area chart series
const throughputByRouteSeries = useMemo(() => {
if (!timeseriesByRoute) return [];
return Object.entries(timeseriesByRoute).map(([routeId, data]) => ({
label: routeId,
data: (data.buckets || []).map((b, i) => ({
x: i as number,
y: b.totalCount,
})),
}));
// Throughput by Route — flat data for ThemedChart
const throughputByRouteData = useMemo(() => {
if (!timeseriesByRoute) return { data: [], keys: [] as string[] };
const entries = Object.entries(timeseriesByRoute);
if (!entries.length) return { data: [], keys: [] as string[] };
const len = entries[0][1].buckets.length;
const keys = entries.map(([routeId]) => routeId);
const data = Array.from({ length: len }, (_, i) => {
const point: Record<string, number | string> = { idx: i };
for (const [routeId, { buckets }] of entries) {
point[routeId] = buckets[i]?.totalCount ?? 0;
}
return point;
});
return { data, keys };
}, [timeseriesByRoute]);
// Latency percentiles chart — P99 line from app-level timeseries
const latencyChartSeries = useMemo(() => {
// Latency percentiles chart — flat data for ThemedChart
const latencyChartData = useMemo(() => {
const buckets = timeseries?.buckets || [];
return [
{
label: 'P99',
data: buckets.map((b, i) => ({
x: i as number,
y: b.p99DurationMs,
})),
},
{
label: 'Avg',
data: buckets.map((b, i) => ({
x: i as number,
y: b.avgDurationMs,
})),
},
];
return buckets.map((b, i) => ({
idx: i,
p99: b.p99DurationMs,
avg: b.avgDurationMs,
}));
}, [timeseries]);
// Error rows with stable identity
@@ -398,22 +395,21 @@ export default function DashboardL2() {
{(timeseries?.buckets?.length ?? 0) > 0 && (
<div className={styles.chartGrid}>
<Card title="Throughput by Route">
<AreaChart
series={throughputByRouteSeries}
yLabel="msg/s"
height={200}
className={styles.chart}
/>
<ThemedChart data={throughputByRouteData.data} height={200} xDataKey="idx" yLabel="msg/s">
{throughputByRouteData.keys.map((key, i) => (
<Area key={key} dataKey={key} name={key} stroke={CHART_COLORS[i % CHART_COLORS.length]}
fill={CHART_COLORS[i % CHART_COLORS.length]} fillOpacity={0.1} strokeWidth={2} dot={false} />
))}
</ThemedChart>
</Card>
<Card title="Latency Percentiles">
<LineChart
series={latencyChartSeries}
yLabel="ms"
threshold={{ value: slaThresholdMs, label: `SLA ${slaThresholdMs}ms` }}
height={200}
className={styles.chart}
/>
<ThemedChart data={latencyChartData} height={200} xDataKey="idx" yLabel="ms">
<Line dataKey="p99" name="P99" stroke={CHART_COLORS[0]} strokeWidth={2} dot={false} />
<Line dataKey="avg" name="Avg" stroke={CHART_COLORS[1]} 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 }} />
</ThemedChart>
</Card>
</div>
)}

View File

@@ -3,8 +3,11 @@ import { useParams } from 'react-router';
import {
KpiStrip,
DataTable,
AreaChart,
LineChart,
ThemedChart,
Area,
Line,
ReferenceLine,
CHART_COLORS,
Card,
MonoText,
Badge,
@@ -285,31 +288,16 @@ export default function DashboardL3() {
[stats, slaThresholdMs, bottleneck, throughputSparkline, windowSeconds],
);
// ── Chart series ────────────────────────────────────────────────────────
const throughputChartSeries = useMemo(() => [{
label: 'Throughput',
data: (timeseries?.buckets || []).map((b: any, i: number) => ({
x: i,
y: b.totalCount,
// ── Chart data ───────────────────────────────────────────────────────────
const chartData = useMemo(() =>
(timeseries?.buckets || []).map((b: any, i: number) => ({
idx: i,
throughput: b.totalCount,
p99: b.p99DurationMs,
errorRate: b.totalCount > 0 ? (b.failedCount / b.totalCount) * 100 : 0,
})),
}], [timeseries]);
const latencyChartSeries = useMemo(() => [{
label: 'P99',
data: (timeseries?.buckets || []).map((b: any, i: number) => ({
x: i,
y: b.p99DurationMs,
})),
}], [timeseries]);
const errorRateChartSeries = useMemo(() => [{
label: 'Error Rate',
data: (timeseries?.buckets || []).map((b: any, i: number) => ({
x: i,
y: b.totalCount > 0 ? (b.failedCount / b.totalCount) * 100 : 0,
})),
color: 'var(--error)',
}], [timeseries]);
[timeseries],
);
// ── Processor table rows ────────────────────────────────────────────────
const processorRows: ProcessorRow[] = useMemo(() => {
@@ -364,28 +352,25 @@ export default function DashboardL3() {
{(timeseries?.buckets?.length ?? 0) > 0 && (
<div className={styles.chartRow}>
<Card title="Throughput">
<AreaChart
series={throughputChartSeries}
yLabel="msg/s"
height={200}
/>
<ThemedChart data={chartData} height={200} xDataKey="idx" 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">
<LineChart
series={latencyChartSeries}
yLabel="ms"
threshold={{ value: slaThresholdMs, label: `SLA ${slaThresholdMs}ms` }}
height={200}
/>
<ThemedChart data={chartData} height={200} xDataKey="idx" 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 }} />
</ThemedChart>
</Card>
<Card title="Error Rate">
<AreaChart
series={errorRateChartSeries}
yLabel="%"
height={200}
/>
<ThemedChart data={chartData} height={200} xDataKey="idx" yLabel="%">
<Area dataKey="errorRate" name="Error Rate" stroke={CHART_COLORS[1]}
fill={CHART_COLORS[1]} fillOpacity={0.1} strokeWidth={2} dot={false} />
</ThemedChart>
</Card>
</div>
)}