import { useMemo } from 'react'; import { useParams } from 'react-router'; import { StatCard, Sparkline, MonoText, Badge, DataTable, AreaChart, LineChart, BarChart, } from '@cameleer/design-system'; import type { Column } from '@cameleer/design-system'; import { useRouteMetrics } from '../../api/queries/catalog'; import { useExecutionStats, useStatsTimeseries } from '../../api/queries/executions'; import { useGlobalFilters } from '@cameleer/design-system'; import styles from './RoutesMetrics.module.css'; interface RouteRow { id: string; routeId: string; appId: string; exchangeCount: number; successRate: number; avgDurationMs: number; p99DurationMs: number; errorRate: number; throughputPerSec: number; sparkline: number[]; } export default function RoutesMetrics() { const { appId, routeId } = useParams(); const { timeRange } = useGlobalFilters(); const timeFrom = timeRange.start.toISOString(); const timeTo = timeRange.end.toISOString(); const { data: metrics } = useRouteMetrics(timeFrom, timeTo, appId); const { data: stats } = useExecutionStats(timeFrom, timeTo, routeId, appId); const { data: timeseries } = useStatsTimeseries(timeFrom, timeTo, routeId, appId); const rows: RouteRow[] = useMemo(() => (metrics || []).map((m: any) => ({ id: `${m.appId}/${m.routeId}`, ...m, })), [metrics], ); const sparklineData = useMemo(() => (timeseries?.buckets || []).map((b: any) => b.totalCount as number), [timeseries], ); const chartData = useMemo(() => (timeseries?.buckets || []).map((b: any) => ({ time: new Date(b.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), throughput: b.totalCount, latency: b.avgDurationMs, errors: b.failedCount, successRate: b.totalCount > 0 ? ((b.totalCount - b.failedCount) / b.totalCount) * 100 : 100, })), [timeseries], ); const columns: Column[] = [ { key: 'routeId', header: 'Route', render: (v) => {String(v)} }, { key: 'appId', header: 'App', render: (v) => }, { key: 'exchangeCount', header: 'Exchanges', sortable: true }, { key: 'successRate', header: 'Success', sortable: true, render: (v) => `${((v as number) * 100).toFixed(1)}%`, }, { key: 'avgDurationMs', header: 'Avg Duration', sortable: true, render: (v) => `${(v as number).toFixed(0)}ms` }, { key: 'p99DurationMs', header: 'P99', sortable: true, render: (v) => `${(v as number).toFixed(0)}ms` }, { key: 'errorRate', header: 'Error Rate', sortable: true, render: (v) => { const rate = v as number; const cls = rate > 0.05 ? styles.rateBad : rate > 0.01 ? styles.rateWarn : styles.rateGood; return {(rate * 100).toFixed(1)}%; }, }, { key: 'sparkline', header: 'Trend', width: '80px', render: (v) => , }, ]; const errorRate = stats?.totalCount ? (((stats.failedCount ?? 0) / stats.totalCount) * 100) : 0; const prevErrorRate = stats?.prevTotalCount ? (((stats.prevFailedCount ?? 0) / stats.prevTotalCount) * 100) : 0; const errorTrend: 'up' | 'down' | 'neutral' = errorRate > prevErrorRate ? 'up' : errorRate < prevErrorRate ? 'down' : 'neutral'; const errorTrendValue = stats?.prevTotalCount ? `${Math.abs(errorRate - prevErrorRate).toFixed(2)}%` : undefined; const p99Ms = stats?.p99LatencyMs ?? 0; const prevP99Ms = stats?.prevP99LatencyMs ?? 0; const latencyTrend: 'up' | 'down' | 'neutral' = p99Ms > prevP99Ms ? 'up' : p99Ms < prevP99Ms ? 'down' : 'neutral'; const latencyTrendValue = prevP99Ms ? `${Math.abs(p99Ms - prevP99Ms)}ms` : undefined; const totalCount = stats?.totalCount ?? 0; const prevTotalCount = stats?.prevTotalCount ?? 0; const throughputTrend: 'up' | 'down' | 'neutral' = totalCount > prevTotalCount ? 'up' : totalCount < prevTotalCount ? 'down' : 'neutral'; const throughputTrendValue = prevTotalCount ? `${Math.abs(((totalCount - prevTotalCount) / prevTotalCount) * 100).toFixed(0)}%` : undefined; const successRate = stats?.totalCount ? (((stats.totalCount - (stats.failedCount ?? 0)) / stats.totalCount) * 100) : 100; const activeCount = stats?.activeCount ?? 0; const errorSparkline = (timeseries?.buckets || []).map((b: any) => b.failedCount as number); const latencySparkline = (timeseries?.buckets || []).map((b: any) => b.p99DurationMs as number); return (
300 ? 'error' : p99Ms > 200 ? 'warning' : 'success'} sparkline={latencySparkline} /> { const failed = errorSparkline[i] ?? 0; return v > 0 ? ((v - failed) / v) * 100 : 100; })} />
Route Metrics {rows.length} routes
{chartData.length > 0 && (
Throughput
({ x: i, y: d.throughput })) }]} height={200} />
Latency
({ x: i, y: d.latency })) }]} height={200} threshold={{ value: 300, label: 'SLA 300ms' }} />
Errors
({ x: d.time as string, y: d.errors })) }]} height={200} />
Success Rate
({ x: i, y: d.successRate })) }]} height={200} />
)}
); }