/** * dashboard-client/src/components/CacheHitRateTrendCard.tsx -- Cache hit-rate * time-series trend chart (A3, PLAN_V2 Phase 4). * * Renders a sparkline view of cache hit rate from the /api/perf endpoint using * real cache_hit_pct samples with timestamps. Each bar represents one sample. * Shows the latest, average, and sample count. * * Uses .ov-sparkline / .ov-sparkline-bar CSS classes from cache.css. * No external charting dependencies. */ import type React from "react"; import type { PerfResponse, CacheHitSample } from "@contracts"; import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card"; export interface CacheHitRateTrendCardProps { perf: PerfResponse; } function fmtPct(n: number): string { return `${n.toFixed(1)}%`; } /** * Map a percentage (0–100) to a CSS color class: * >= 80: green (healthy) * >= 60: teal (fair) * >= 40: yellow (degraded) * >= 20: orange (poor) * < 20: red (critical) */ function pctColorClass(pct: number): string { if (pct >= 80) return "ov-bar-green"; if (pct >= 60) return "ov-bar-teal"; if (pct >= 40) return "ov-bar-yellow"; if (pct >= 20) return "ov-bar-orange"; return "ov-bar-red"; } export const CacheHitRateTrendCard: React.FC = ({ perf, }) => { const { cache_hit_pct: c } = perf; const samples: CacheHitSample[] = c.samples && c.samples.length > 0 ? c.samples : []; return ( Cache Hit-Rate Trend {/* Metric summary */}
Avg: {fmtPct(c.avg)}
Latest: {fmtPct(c.latest)}
Samples: {c.n}
{/* Sparkline bars from real samples */} {samples.length > 0 ? ( <>
{samples.map((s, i) => { const barH = Math.max(2, Math.round(s.pct * 0.6)); const colorClass = pctColorClass(s.pct); const isLatest = i === samples.length - 1; return (
); })}
{samples.length} sample{samples.length !== 1 ? "s" : ""} over{" "} {samples.length > 1 ? `${Math.round( (samples[samples.length - 1].ts - samples[0].ts) / 1000, )}s` : "latest turn"}
) : (
No cache-hit samples yet
)} ); };