feat(review): review queue for leads + manual reviewer reassignment
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

Leads get the same two-queue view operators already have: 'Available
to Review' (pending reviews currently assigned to a peer lead) and
'My Assigned Reviews' (assigned to me), replacing the old single
'My Reviews' toggle. Since the load-balanced auto-assignment always
picks a reviewer immediately, 'available' means peer-assigned reviews
a lead could pick up if the assignee can't get to them, not unclaimed
ones (there aren't any).

POST /tests/{id}/assign now also accepts red_reviewer_assignee /
blue_reviewer_assignee, validated against the matching lead role and
synced to Jira the same way operator assignment already is. The
AssigneeControl UI gained a 'reviewer' kind (leads-only picker) shown
on the test detail header while a test sits in red_review/blue_review
— this also fixes the reviewer assignment being invisible in Aegis
even though it was already being pushed to Jira correctly.
This commit is contained in:
kitos
2026-07-10 16:19:14 +02:00
parent be2a3fdced
commit 0fc2427f71
8 changed files with 250 additions and 72 deletions
@@ -93,7 +93,10 @@ interface TestDetailHeaderProps {
onUpdateClassification: (value: DataClassification) => void;
isUpdatingClassification: boolean;
operators: OperatorOut[];
onAssignOperator: (side: "red" | "blue", userId: string | null) => void;
onAssignOperator: (
field: "red_tech_assignee" | "blue_tech_assignee" | "red_reviewer_assignee" | "blue_reviewer_assignee",
userId: string | null,
) => void;
isAssigningOperator: boolean;
}
@@ -551,13 +554,37 @@ export default function TestDetailHeader({
<div className="flex flex-col items-end gap-2">
<div className="flex items-center gap-2">
{test.state === "red_review" && (
<AssigneeControl
side="red"
kind="reviewer"
assigneeId={test.red_reviewer_assignee}
operators={operators}
canEdit={role === "red_lead" || role === "manager"}
isSaving={isAssigningOperator}
onAssign={(userId) => onAssignOperator("red_reviewer_assignee", userId)}
size="lg"
/>
)}
{test.state === "blue_review" && (
<AssigneeControl
side="blue"
kind="reviewer"
assigneeId={test.blue_reviewer_assignee}
operators={operators}
canEdit={role === "blue_lead" || role === "manager"}
isSaving={isAssigningOperator}
onAssign={(userId) => onAssignOperator("blue_reviewer_assignee", userId)}
size="lg"
/>
)}
<AssigneeControl
side="red"
assigneeId={test.red_tech_assignee}
operators={operators}
canEdit={role === "red_lead" || role === "manager"}
isSaving={isAssigningOperator}
onAssign={(userId) => onAssignOperator("red", userId)}
onAssign={(userId) => onAssignOperator("red_tech_assignee", userId)}
size="lg"
/>
<AssigneeControl
@@ -566,7 +593,7 @@ export default function TestDetailHeader({
operators={operators}
canEdit={role === "blue_lead" || role === "manager"}
isSaving={isAssigningOperator}
onAssign={(userId) => onAssignOperator("blue", userId)}
onAssign={(userId) => onAssignOperator("blue_tech_assignee", userId)}
size="lg"
/>
</div>