import { useMemo } from 'react'; import { useGlobalFilters } from '@cameleer/design-system'; import { useExecutionStats } from '../api/queries/executions'; import type { Scope } from '../hooks/useScope'; import styles from './TabKpis.module.css'; interface TabKpisProps { scope: Scope; } function formatNum(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; return n.toLocaleString(); } function formatMs(ms: number): string { if (ms >= 60_000) return `${(ms / 1000).toFixed(0)}s`; if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`; return `${Math.round(ms)}ms`; } function formatPct(pct: number): string { return `${pct.toFixed(1)}%`; } type Trend = 'up' | 'down' | 'flat'; function trend(current: number, previous: number): Trend { if (current > previous) return 'up'; if (current < previous) return 'down'; return 'flat'; } function trendArrow(t: Trend): string { switch (t) { case 'up': return '\u2191'; case 'down': return '\u2193'; case 'flat': return '\u2192'; } } interface Metric { label: string; value: string; trend: Trend; /** Whether "up" is bad (e.g. error rate, latency) */ upIsBad?: boolean; } export function TabKpis({ scope }: TabKpisProps) { const { timeRange } = useGlobalFilters(); const timeFrom = timeRange.start.toISOString(); const timeTo = timeRange.end.toISOString(); const { data: stats } = useExecutionStats( timeFrom, timeTo, scope.routeId, scope.appId, ); const metrics: Metric[] = useMemo(() => { if (!stats) return []; const total = stats.totalCount ?? 0; const failed = stats.failedCount ?? 0; const prevTotal = stats.prevTotalCount ?? 0; const prevFailed = stats.prevFailedCount ?? 0; const errorRate = total > 0 ? (failed / total) * 100 : 0; const prevErrorRate = prevTotal > 0 ? (prevFailed / prevTotal) * 100 : 0; const avgMs = stats.avgDurationMs ?? 0; const prevAvgMs = stats.prevAvgDurationMs ?? 0; const p99 = stats.p99LatencyMs ?? 0; const prevP99 = stats.prevP99LatencyMs ?? 0; return [ { label: 'Total', value: formatNum(total), trend: trend(total, prevTotal) }, { label: 'Err%', value: formatPct(errorRate), trend: trend(errorRate, prevErrorRate), upIsBad: true }, { label: 'Avg', value: formatMs(avgMs), trend: trend(avgMs, prevAvgMs), upIsBad: true }, { label: 'P99', value: formatMs(p99), trend: trend(p99, prevP99), upIsBad: true }, ]; }, [stats]); if (metrics.length === 0) return null; return (