feat(techniques): show test status on template cards
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
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 <noreply@anthropic.com>
This commit is contained in:
@@ -373,11 +373,70 @@ export default function TechniqueDetailPage() {
|
||||
</div>
|
||||
) : templates.length > 0 ? (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{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<Record<TestState, string>> = {
|
||||
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 (
|
||||
<div
|
||||
key={tpl.id}
|
||||
className="flex items-center justify-between rounded-lg border border-gray-800 bg-gray-800/30 p-4 transition-colors hover:border-gray-700"
|
||||
className="flex items-start justify-between rounded-lg border border-gray-800 bg-gray-800/30 p-4 transition-colors hover:border-gray-700"
|
||||
>
|
||||
{/* Left: template info */}
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium text-gray-200">{tpl.name}</p>
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
@@ -396,15 +455,60 @@ export default function TechniqueDetailPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setTemplateFormId(tpl.id)}
|
||||
className="ml-3 flex shrink-0 items-center gap-1 rounded-lg border border-cyan-500/30 bg-cyan-500/10 px-3 py-1.5 text-xs font-medium text-cyan-400 hover:bg-cyan-500/20 transition-colors"
|
||||
>
|
||||
<FlaskConical className="h-3.5 w-3.5" />
|
||||
Run This Test
|
||||
</button>
|
||||
|
||||
{/* Right: status + action */}
|
||||
<div className="ml-3 flex shrink-0 flex-col items-end gap-2">
|
||||
{/* ── Status badge ── */}
|
||||
{activeTest && (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-blue-500/30 bg-blue-500/10 px-2 py-0.5 text-[10px] font-medium text-blue-400">
|
||||
<Clock className="h-2.5 w-2.5" />
|
||||
{ACTIVE_LABEL[activeTest.state as TestState] ?? activeTest.state.replace(/_/g, " ")}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!activeTest && latestValidated && (
|
||||
<div className="flex flex-col items-end gap-0.5">
|
||||
<span className={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-medium ${detBadgeStyle}`}>
|
||||
{detResult === "detected"
|
||||
? <CheckCircle className="h-2.5 w-2.5" />
|
||||
: <AlertTriangle className="h-2.5 w-2.5" />
|
||||
}
|
||||
{detLabel}
|
||||
</span>
|
||||
{isStale && (
|
||||
<span className="text-[9px] font-medium text-amber-400">
|
||||
⚠ Coverage may be stale
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Action button ── */}
|
||||
{activeTest ? (
|
||||
<button
|
||||
onClick={() => navigate(`/tests/${activeTest.id}`)}
|
||||
className="flex items-center gap-1 rounded-lg border border-blue-500/30 bg-blue-500/10 px-3 py-1.5 text-xs font-medium text-blue-400 hover:bg-blue-500/20 transition-colors"
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
View test
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setTemplateFormId(tpl.id)}
|
||||
className={`flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||
latestValidated && detResult === "detected" && !isStale
|
||||
? "border-gray-600/30 bg-gray-800/50 text-gray-400 hover:border-gray-600 hover:text-gray-300"
|
||||
: "border-cyan-500/30 bg-cyan-500/10 text-cyan-400 hover:bg-cyan-500/20"
|
||||
}`}
|
||||
>
|
||||
<FlaskConical className="h-3.5 w-3.5" />
|
||||
{needsReRun ? "Run This Test" : latestValidated ? "Re-run" : "Run This Test"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-8 text-gray-400">
|
||||
|
||||
Reference in New Issue
Block a user