From 9d66c30127d295250904a8bcb4ab1aeeb75e24f9 Mon Sep 17 00:00:00 2001 From: kitos Date: Tue, 14 Jul 2026 11:03:17 +0200 Subject: [PATCH] feat(tests): add team queue overview section for leads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit red_lead/blue_lead never saw a plain "All Tests" list — they were always routed into their own review two-queue view or "My Tasks", with no way to see every test and who's actually working it. Adds an always-visible section at the end of the Tests page for leads showing every test with both Red and Blue assignee columns, so they can monitor the queues and spot a stuck ticket or an overloaded operator. The list endpoint (GET /tests) now joinedloads the assignee relationships and resolves usernames the same way the detail endpoint already does, so the new section doesn't depend on GET /users/operators (lead/manager-only). --- backend/app/services/test_crud_service.py | 5 +- backend/tests/test_test_assignee_username.py | 21 ++++++++ frontend/src/pages/TestsPage.tsx | 52 ++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/backend/app/services/test_crud_service.py b/backend/app/services/test_crud_service.py index 2ebbf8b..44eedd2 100644 --- a/backend/app/services/test_crud_service.py +++ b/backend/app/services/test_crud_service.py @@ -70,7 +70,10 @@ def _build_test_query( Kept as a single source of truth so the displayed page of results and the total count it's paginated against can never drift apart. """ - query = db.query(Test).options(joinedload(Test.technique)) + query = db.query(Test).options( + joinedload(Test.technique), + joinedload(Test.red_tech_assigned_user), joinedload(Test.blue_tech_assigned_user), + ) # Check: state if state: diff --git a/backend/tests/test_test_assignee_username.py b/backend/tests/test_test_assignee_username.py index 42f4105..1a2b4b8 100644 --- a/backend/tests/test_test_assignee_username.py +++ b/backend/tests/test_test_assignee_username.py @@ -61,3 +61,24 @@ def test_assignee_username_is_none_when_unassigned(client, db, red_lead_headers, body = resp.json() assert body["red_tech_assignee"] is None assert body["red_tech_assignee_username"] is None + + +def test_list_endpoint_resolves_assignee_usernames_for_team_queue_overview( + client, db, red_lead_headers, red_lead_user, red_tech_user, blue_tech_user, +): + """The leads' 'All Tests — Team Queue Overview' section reads assignee + usernames off GET /tests (the list endpoint), not just the detail + endpoint — both must resolve them the same way.""" + technique = _seed_technique(db) + _seed_test( + db, technique, red_lead_user.id, + red_tech_assignee=red_tech_user.id, blue_tech_assignee=blue_tech_user.id, + ) + + resp = client.get("/api/v1/tests", headers=red_lead_headers) + + assert resp.status_code == 200, resp.text + body = resp.json() + match = next(t for t in body if t["red_tech_assignee"] == str(red_tech_user.id)) + assert match["red_tech_assignee_username"] == red_tech_user.username + assert match["blue_tech_assignee_username"] == blue_tech_user.username diff --git a/frontend/src/pages/TestsPage.tsx b/frontend/src/pages/TestsPage.tsx index dbd210f..e8cf7c9 100644 --- a/frontend/src/pages/TestsPage.tsx +++ b/frontend/src/pages/TestsPage.tsx @@ -706,6 +706,38 @@ export default function TestsPage() { )} + + {/* ── Team queue overview for leads ───────────────────────────── + red_lead/blue_lead never hit the plain "All Tests" branch above — + they're always routed into the review two-queue view or their + own "My Tasks" — so they had no way to see every test and who's + working it, which is what they need to spot a stuck ticket or an + overloaded operator. Always shown at the end, independent of the + my-tasks toggle above, with assignees visible on both sides. */} + {isReviewLead && ( +
+
+
+

All Tests — Team Queue Overview

+ + {tests.length} + +
+ Every test, with who's assigned on each side +
+ +
+ )} ); } @@ -721,6 +753,7 @@ function TestTable({ navigate, formatDate, emptyMessage, + showAssignees = false, }: { tests: Test[]; columns: { key: SortKey; label: string; cls: string }[]; @@ -730,6 +763,9 @@ function TestTable({ navigate: (path: string) => void; formatDate: (d: string | null | undefined) => string; emptyMessage: string; + /** Adds Red/Blue assignee columns — for the leads' team queue overview, + * where seeing who's working each side is the whole point. */ + showAssignees?: boolean; }) { return (
@@ -759,6 +795,12 @@ function TestTable({ ))} + {showAssignees && ( + <> + Red Assignee + Blue Assignee + + )} Action @@ -819,6 +861,16 @@ function TestTable({ {formatDate(lastActivityDate(test))} + {showAssignees && ( + <> + + {test.red_tech_assignee_username || "-"} + + + {test.blue_tech_assignee_username || "-"} + + + )}