From 727b8af7fd18b298533d452f386a325d1c493ce5 Mon Sep 17 00:00:00 2001 From: kitos Date: Fri, 29 May 2026 10:59:39 +0200 Subject: [PATCH] feat(techniques): show test status on template cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each template card in 'Available Test Templates' now shows contextual status derived from technique.tests (already loaded): - Active test (draft/executing/evaluating/in_review): blue 'Executing / In Review' badge + 'View test →' button (prevents blind duplicate creation) - Validated / detected (fresh): green 'Detected' badge + dimmed 'Re-run' button - Validated / not_detected or partial: red/yellow result badge + full 'Run This Test' button (re-run encouraged) - Validated but stale (review_required=true): result badge + '⚠ Coverage may be stale' line - No tests: normal 'Run This Test' button No extra API calls — status is derived from the technique detail already in-memory. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/pages/TechniqueDetailPage.tsx | 124 +++++++++++++++++++-- 1 file changed, 114 insertions(+), 10 deletions(-) diff --git a/frontend/src/pages/TechniqueDetailPage.tsx b/frontend/src/pages/TechniqueDetailPage.tsx index 83bd6d0..50d6b6c 100644 --- a/frontend/src/pages/TechniqueDetailPage.tsx +++ b/frontend/src/pages/TechniqueDetailPage.tsx @@ -373,11 +373,70 @@ export default function TechniqueDetailPage() { ) : templates.length > 0 ? (
- {templates.map((tpl) => ( + {templates.map((tpl) => { + // ── Derive test status for this technique ────────────── + const allTests = (technique as any).tests ?? []; + + // Any test currently in a non-terminal state + const ACTIVE_STATES: TestState[] = [ + "draft", "red_executing", "blue_evaluating", "in_review", + ]; + const activeTest = allTests.find( + (t: { state: TestState }) => ACTIVE_STATES.includes(t.state) + ) ?? null; + + // Most recent validated test + const latestValidated: { + id: string; + detection_result: TestResult | null; + updated_at: string | null; + created_at: string; + } | null = + [...allTests] + .filter((t: { state: TestState }) => t.state === "validated") + .sort((a: { updated_at: string | null; created_at: string }, b: { updated_at: string | null; created_at: string }) => + (b.updated_at || b.created_at).localeCompare(a.updated_at || a.created_at) + )[0] ?? null; + + // Coverage is stale if the sync job raised the review flag + // (only meaningful when we already have a validated test) + const isStale = technique.review_required && !!latestValidated; + + // ── Helpers ──────────────────────────────────────────── + const ACTIVE_LABEL: Partial> = { + draft: "Draft", + red_executing: "Executing", + blue_evaluating: "Evaluating", + in_review: "In Review", + }; + + const detResult = latestValidated?.detection_result; + const detBadgeStyle = + detResult === "detected" + ? "border-green-500/30 bg-green-500/10 text-green-400" + : detResult === "partially_detected" + ? "border-yellow-500/30 bg-yellow-500/10 text-yellow-400" + : detResult === "not_detected" + ? "border-red-500/30 bg-red-500/10 text-red-400" + : "border-gray-600/30 bg-gray-800/50 text-gray-400"; + + const detLabel = + detResult === "detected" ? "Detected" + : detResult === "partially_detected" ? "Partial" + : detResult === "not_detected" ? "Not detected" + : "Validated"; + + const needsReRun = + !activeTest && + latestValidated && + (detResult !== "detected" || isStale); + + return (
+ {/* Left: template info */}

{tpl.name}

@@ -396,15 +455,60 @@ export default function TechniqueDetailPage() { )}
- + + {/* Right: status + action */} +
+ {/* ── Status badge ── */} + {activeTest && ( + + + {ACTIVE_LABEL[activeTest.state as TestState] ?? activeTest.state.replace(/_/g, " ")} + + )} + + {!activeTest && latestValidated && ( +
+ + {detResult === "detected" + ? + : + } + {detLabel} + + {isStale && ( + + ⚠ Coverage may be stale + + )} +
+ )} + + {/* ── Action button ── */} + {activeTest ? ( + + ) : ( + + )} +
- ))} + ); + })}
) : (