/** * TestPhaseTimeline * * Read-only timeline showing the automated phase durations for a test: * 1. Red Team Execution (red_started_at → blue_started_at, minus paused) * 2. Blue Queue (blue_started_at → blue_work_started_at) * 3. Blue Evaluation (blue_work_started_at → …, open-ended until validated) * 4. Red Lead Validation (red_validated_at + status) * 5. Blue Lead Validation (blue_validated_at + status) * * Only phases with at least a start timestamp are rendered. */ import { Clock, Sword, Shield, CheckCircle, XCircle, Timer } from "lucide-react"; import type { Test } from "../types/models"; // ── Helpers ────────────────────────────────────────────────────────── /** Parse a backend datetime string (may lack 'Z') to a JS Date. */ function parseDate(s: string): Date { return new Date(s.endsWith("Z") ? s : s + "Z"); } /** Compute duration in seconds between two timestamps, minus any paused seconds. */ function durationSeconds(start: string, end: string, pausedSecs = 0): number { const ms = parseDate(end).getTime() - parseDate(start).getTime(); return Math.max(0, Math.floor(ms / 1000) - pausedSecs); } /** Human-readable duration string. */ function fmtDuration(secs: number): string { if (secs < 60) return `${secs}s`; if (secs < 3600) return `${Math.floor(secs / 60)}m`; const h = Math.floor(secs / 3600); const m = Math.floor((secs % 3600) / 60); return m > 0 ? `${h}h ${m}m` : `${h}h`; } /** Short date + time label. */ function fmtTs(s: string): string { return parseDate(s).toLocaleDateString("en-US", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); } // ── Phase row ───────────────────────────────────────────────────────── interface PhaseRowProps { dotClass: string; icon: React.ReactNode; label: string; badge?: React.ReactNode; startTs: string | null; duration?: number | null; // seconds; null → still running / unknown isLast?: boolean; } function PhaseRow({ dotClass, icon, label, badge, startTs, duration, isLast }: PhaseRowProps) { return (
{/* Connector line */} {!isLast && (
)} {/* Dot */}
{/* Content */}
{icon} {label} {badge} {duration != null && ( {fmtDuration(duration)} )}
{startTs && (

{fmtTs(startTs)}

)}
); } // ── Validation badge ────────────────────────────────────────────────── function ValidationBadge({ status }: { status: string | null }) { if (!status) return null; if (status === "approved") return ( Approved ); if (status === "rejected") return ( Rejected ); return ( {status} ); } // ── Main component ──────────────────────────────────────────────────── interface TestPhaseTimelineProps { test: Test; } export default function TestPhaseTimeline({ test }: TestPhaseTimelineProps) { const { red_started_at, blue_started_at, blue_work_started_at, red_paused_seconds, blue_paused_seconds, red_validated_at, blue_validated_at, red_validation_status, blue_validation_status, } = test; // Compute per-phase durations const redExecSecs = red_started_at && blue_started_at ? durationSeconds(red_started_at, blue_started_at, red_paused_seconds) : null; const blueQueueSecs = blue_started_at && blue_work_started_at ? durationSeconds(blue_started_at, blue_work_started_at) : null; // Blue evaluation: blue_work_started_at → first validation timestamp const blueEvalEnd = red_validated_at || blue_validated_at || null; const blueEvalSecs = blue_work_started_at && blueEvalEnd ? durationSeconds(blue_work_started_at, blueEvalEnd, blue_paused_seconds) : null; // Determine which phases to show (need at least a start timestamp) const hasRedExec = !!red_started_at; const hasBlueQueue = !!blue_started_at; const hasBlueEval = !!blue_work_started_at; const hasRedValidation = !!red_validated_at; const hasBlueValidation = !!blue_validated_at; const anyPhase = hasRedExec || hasBlueQueue || hasBlueEval || hasRedValidation || hasBlueValidation; if (!anyPhase) return null; // Count rendered phases for isLast detection const phases = [ hasRedExec, hasBlueQueue, hasBlueEval, hasRedValidation, hasBlueValidation, ]; const lastIdx = phases.lastIndexOf(true); let phaseIdx = -1; return (

Phase Timeline

{hasRedExec && ( } label="Red Team Execution" startTs={red_started_at} duration={redExecSecs} isLast={++phaseIdx === lastIdx} /> )} {hasBlueQueue && ( } label="Blue Queue" startTs={blue_started_at} duration={blueQueueSecs} isLast={++phaseIdx === lastIdx} /> )} {hasBlueEval && ( } label="Blue Evaluation" startTs={blue_work_started_at} duration={blueEvalSecs} isLast={++phaseIdx === lastIdx} /> )} {hasRedValidation && ( } label="Red Lead Validation" badge={} startTs={red_validated_at} duration={null} isLast={++phaseIdx === lastIdx} /> )} {hasBlueValidation && ( } label="Blue Lead Validation" badge={} startTs={blue_validated_at} duration={null} isLast={++phaseIdx === lastIdx} /> )}
); }