From 1120d8f2ce1b8645f99b89238c897edb5d267e70 Mon Sep 17 00:00:00 2001 From: kitos Date: Thu, 28 May 2026 17:18:21 +0200 Subject: [PATCH] feat(tests): add Validated Tests as dedicated page, remove duplicate sidebar entry - New /tests/validated page with its own route and sidebar link, showing only validated tests with Attack and Detection result badges. - Removed the duplicate "My Pending Tasks" sidebar entry (same as All Tests). - All Tests table no longer shows validated tests; clicking the Validated counter card navigates to the new page instead. - Validated option removed from the state filter dropdown in All Tests. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/App.tsx | 2 + frontend/src/components/Sidebar.tsx | 4 +- frontend/src/pages/TestsPage.tsx | 208 ++------------- frontend/src/pages/ValidatedTestsPage.tsx | 310 ++++++++++++++++++++++ 4 files changed, 335 insertions(+), 189 deletions(-) create mode 100644 frontend/src/pages/ValidatedTestsPage.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8de2a4d..78ef499 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,6 +18,7 @@ const TestsPage = React.lazy(() => import("./pages/TestsPage")); const TestCreatePage = React.lazy(() => import("./pages/TestCreatePage")); const TestDetailPage = React.lazy(() => import("./pages/TestDetailPage")); const TestCatalogPage = React.lazy(() => import("./pages/TestCatalogPage")); +const ValidatedTestsPage = React.lazy(() => import("./pages/ValidatedTestsPage")); const ReportsPage = React.lazy(() => import("./pages/ReportsPage")); const SystemPage = React.lazy(() => import("./pages/SystemPage")); const UsersPage = React.lazy(() => import("./pages/UsersPage")); @@ -65,6 +66,7 @@ export default function App() { {/* ── Tests ────────────────────────────────────────────── */} }>} /> }>} /> + }>} /> }>} /> }>} /> }>} /> diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 6e38e7f..499519b 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -10,7 +10,7 @@ import { FileText, ChevronDown, ListChecks, - ClipboardList, + CheckCircle, Database, Crosshair, Zap, @@ -41,7 +41,7 @@ const mainLinks: NavItem[] = [ icon: FlaskConical, children: [ { to: "/tests", label: "All Tests", icon: ListChecks }, - { to: "/tests?view=pending", label: "My Pending Tasks", icon: ClipboardList }, + { to: "/tests/validated", label: "Validated Tests", icon: CheckCircle }, { to: "/test-catalog", label: "Test Catalog", icon: BookOpen }, ], }, diff --git a/frontend/src/pages/TestsPage.tsx b/frontend/src/pages/TestsPage.tsx index 13e2626..805bfbd 100644 --- a/frontend/src/pages/TestsPage.tsx +++ b/frontend/src/pages/TestsPage.tsx @@ -103,9 +103,6 @@ export default function TestsPage() { const [searchText, setSearchText] = useState(""); const [showMyTasks, setShowMyTasks] = useState(false); - // ── Validated section toggle ────────────────────────────────────── - const [showValidated, setShowValidated] = useState(false); - // ── Sort state ──────────────────────────────────────────────────── type SortKey = | "name" @@ -124,7 +121,6 @@ export default function TestsPage() { setSortDir((d) => (d === "asc" ? "desc" : "asc")); } else { setSortKey(key); - // For waiting_time, default to asc (oldest = most urgent first) setSortDir(key === "waiting_time" ? "asc" : "asc"); } }; @@ -169,12 +165,6 @@ export default function TestsPage() { queryFn: () => getTests(filters), }); - // Dedicated query for validated tests (shown in separate section) - const { data: validatedTests } = useQuery({ - queryKey: ["tests", "validated"], - queryFn: () => getTests({ state: "validated", limit: 200 }), - }); - // Client-side filtering const tests = useMemo(() => { if (!allTests) return []; @@ -187,8 +177,7 @@ export default function TestsPage() { ); } - // Exclude validated from the main active-tests table - // (unless the user explicitly chose to filter by validated) + // Exclude validated from this table — they live in /tests/validated if (stateFilter !== "validated") { filtered = filtered.filter((t) => t.state !== "validated"); } @@ -239,14 +228,12 @@ export default function TestsPage() { bv = b.updated_at || ""; break; case "waiting_time": { - // Both blue_evaluating: oldest updated_at = longest wait const aIsBlue = a.state === "blue_evaluating"; const bIsBlue = b.state === "blue_evaluating"; if (aIsBlue && bIsBlue) { av = a.updated_at || ""; bv = b.updated_at || ""; } else { - // Blue_evaluating entries come first when sorting by wait if (aIsBlue && !bIsBlue) return sortDir === "asc" ? -1 : 1; if (!aIsBlue && bIsBlue) return sortDir === "asc" ? 1 : -1; av = a.updated_at || ""; @@ -263,18 +250,6 @@ export default function TestsPage() { }, [allTests, searchText, showMyTasks, user, stateFilter, sortKey, sortDir]); // ── State counters ──────────────────────────────────────────────── - const stateCounts = useMemo(() => { - const counts: Record = {}; - for (const s of ALL_STATES) counts[s] = 0; - if (allTests) { - for (const t of allTests) { - counts[t.state] = (counts[t.state] || 0) + 1; - } - } - return counts; - }, [allTests]); - - // Count from unfiltered query for the top cards const { data: allTestsUnfiltered } = useQuery({ queryKey: ["tests", "unfiltered-counts"], queryFn: () => getTests({ limit: 200 }), @@ -296,7 +271,8 @@ export default function TestsPage() { // ── Formatting helpers ───────────────────────────────────────────── const formatDate = (dateStr: string | null | undefined) => { if (!dateStr) return "-"; - const utc = dateStr.endsWith("Z") || dateStr.includes("+") ? dateStr : dateStr + "Z"; + const utc = + dateStr.endsWith("Z") || dateStr.includes("+") ? dateStr : dateStr + "Z"; return new Date(utc).toLocaleDateString("es-ES", { year: "numeric", month: "short", @@ -321,35 +297,6 @@ export default function TestsPage() { } }, [user]); - // ── Validated sort helpers ───────────────────────────────────────── - const [valSortKey, setValSortKey] = useState<"name" | "technique" | "created_at" | "updated_at">("updated_at"); - const [valSortDir, setValSortDir] = useState<"asc" | "desc">("desc"); - - const sortedValidatedTests = useMemo(() => { - if (!validatedTests) return []; - return [...validatedTests].sort((a, b) => { - let av = ""; - let bv = ""; - switch (valSortKey) { - case "name": av = a.name.toLowerCase(); bv = b.name.toLowerCase(); break; - case "technique": av = (a.technique_mitre_id || "").toLowerCase(); bv = (b.technique_mitre_id || "").toLowerCase(); break; - case "created_at": av = a.created_at || ""; bv = b.created_at || ""; break; - case "updated_at": av = a.updated_at || ""; bv = b.updated_at || ""; break; - } - const cmp = av < bv ? -1 : av > bv ? 1 : 0; - return valSortDir === "asc" ? cmp : -cmp; - }); - }, [validatedTests, valSortKey, valSortDir]); - - const handleValSort = (key: typeof valSortKey) => { - if (valSortKey === key) { - setValSortDir((d) => (d === "asc" ? "desc" : "asc")); - } else { - setValSortKey(key); - setValSortDir("desc"); - } - }; - // ── Render ───────────────────────────────────────────────────────── if (isLoading) { @@ -424,6 +371,10 @@ export default function TestsPage() {