fix(tests): surface cross-validation and disputed tests in the review queue
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled

Cross-validation (in_review) tests awaiting a lead's vote were only
reachable via the 'My Tasks' toggle, so the default review queue view
never showed them despite being exactly the kind of work a lead
expects to find there. Added an 'Awaiting My Validation' section to
the review queue, and a standalone 'Disputed' section — always
fetched independently of other filters, shown above everything else,
and only rendered when non-empty — since disputed tests need the
most urgent attention.
This commit is contained in:
kitos
2026-07-15 11:41:29 +02:00
parent 8973f199b8
commit 32f4fd25bd
+70 -3
View File
@@ -365,9 +365,18 @@ export default function TestsPage() {
// The auto load-balancer always assigns a reviewer immediately, so
// "available" here means "assigned to another lead" — visible so a
// lead can pick up a peer's review if they can't get to it.
const { availableReviews, myAssignedReviews } = useMemo(() => {
//
// Also includes cross-validation (in_review) tests still awaiting this
// lead's vote — previously only surfaced by toggling "My Tasks", which
// meant a lead landing on this page by default never saw them here at
// all despite this being "the review queue" as far as they're concerned.
const { availableReviews, myAssignedReviews, pendingMyValidation } = useMemo(() => {
if (!isReviewLead || !user || !allTests || showMyTasks) {
return { availableReviews: [] as typeof tests, myAssignedReviews: [] as typeof tests };
return {
availableReviews: [] as typeof tests,
myAssignedReviews: [] as typeof tests,
pendingMyValidation: [] as typeof tests,
};
}
let searchFiltered = allTests;
@@ -391,9 +400,24 @@ export default function TestsPage() {
const inState = searchFiltered.filter((t) => t.state === reviewState);
const available = inState.filter((t) => t[reviewerField] !== user.id);
const mine = inState.filter((t) => t[reviewerField] === user.id);
return { availableReviews: available, myAssignedReviews: mine };
const validationField = user.role === "red_lead" ? "red_validation_status" : "blue_validation_status";
const pendingValidation = searchFiltered.filter(
(t) => t.state === "in_review" && !t[validationField]
);
return { availableReviews: available, myAssignedReviews: mine, pendingMyValidation: pendingValidation };
}, [isReviewLead, user, allTests, searchText, platformFilter, showMyTasks]);
// ── Disputed tests — always fetched independently of the my-tasks
// toggle/state filter so this alert never silently disappears just
// because the lead is looking at a different view. ──────────────────
const { data: disputedTests = [] } = useQuery({
queryKey: ["tests", "disputed-queue"],
queryFn: () => getTests({ state: "disputed", limit: 200 }),
enabled: isReviewLead,
});
// ── Formatting helpers ─────────────────────────────────────────────
const formatDate = (dateStr: string | null | undefined) => {
if (!dateStr) return "-";
@@ -637,6 +661,32 @@ export default function TestsPage() {
)}
</div>
{/* ── Disputed tests — most urgent, always on top when present ──── */}
{isReviewLead && disputedTests.length > 0 && (
<div className="rounded-xl border border-amber-500/40 bg-amber-500/5 p-6">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-amber-400" />
<h2 className="text-lg font-semibold text-amber-400">Disputed</h2>
<span className="rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 text-xs font-medium text-amber-400">
{disputedTests.length}
</span>
</div>
<span className="text-xs text-amber-400/70">Leads disagree on the outcome needs resolution</span>
</div>
<TestTable
tests={disputedTests}
columns={mainTableColumns}
sortKey={sortKey}
sortDir={sortDir}
handleSort={handleSort}
navigate={navigate}
formatDate={formatDate}
emptyMessage=""
/>
</div>
)}
{/* ── Tests Table / Two-Queue View ─────────────────────────────── */}
{techRole ? (
/* Two-section queue for red_tech and blue_tech */
@@ -699,6 +749,23 @@ export default function TestsPage() {
</div>
<TestTable tests={myAssignedReviews} columns={mainTableColumns} sortKey={sortKey} sortDir={sortDir} handleSort={handleSort} navigate={navigate} formatDate={formatDate} emptyMessage="No reviews currently assigned to you." />
</div>
{/* Awaiting My Validation — cross-validation stage (in_review),
distinct from the red_review/blue_review gate above: both
leads act independently here, so there's no "assigned to
someone else" bucket, just "have I voted yet or not". */}
<div className="rounded-xl border border-gray-800 bg-gray-900 p-6">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<h2 className="text-lg font-semibold text-white">Awaiting My Validation</h2>
<span className="rounded-full border border-blue-500/30 bg-blue-500/10 px-2 py-0.5 text-xs font-medium text-blue-400">
{pendingMyValidation.length}
</span>
</div>
<span className="text-xs text-gray-500">Cross-validation both leads vote independently</span>
</div>
<TestTable tests={pendingMyValidation} columns={mainTableColumns} sortKey={sortKey} sortDir={sortDir} handleSort={handleSort} navigate={navigate} formatDate={formatDate} emptyMessage="Nothing awaiting your validation right now." />
</div>
</div>
) : (
/* Normal single-table view for admin, viewer, and leads viewing "My Tasks" */