feat(assign): let leads manually assign operators + sync Jira; send platform as Jira label
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
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
Adds GET /users/operators (leads+admin) and wires a lead-only assign
control into the test detail header, calling the existing but
previously unreachable POST /tests/{id}/assign endpoint. Manual
assignment now also pushes the Jira ticket assignee immediately
instead of waiting for the operator to start execution.
Also adds test.platform as a kebab-case label on Jira ticket creation.
This commit is contained in:
@@ -23,9 +23,11 @@ import {
|
||||
holdTest,
|
||||
resumeTest,
|
||||
updateTestClassification,
|
||||
assignTestOperators,
|
||||
getTestTimeline,
|
||||
getRetestChain,
|
||||
} from "../api/tests";
|
||||
import { getOperators } from "../api/users";
|
||||
import { uploadEvidence, getEvidence } from "../api/evidence";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import type { TestResult, ContainmentResult, AttackSuccessResult, DataClassification, TeamSide, TestTimelineEntry } from "../types/models";
|
||||
@@ -133,6 +135,14 @@ export default function TestDetailPage() {
|
||||
enabled: !!testId && !!test && (test.retest_of !== null || test.retest_count > 0),
|
||||
});
|
||||
|
||||
const canAssignOperators = ["red_lead", "blue_lead", "admin"].includes(user?.role ?? "");
|
||||
const { data: operators = [] } = useQuery({
|
||||
queryKey: ["operators"],
|
||||
queryFn: getOperators,
|
||||
enabled: canAssignOperators,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
// Hydrate drafts from test data
|
||||
useEffect(() => {
|
||||
if (test) {
|
||||
@@ -368,6 +378,16 @@ export default function TestDetailPage() {
|
||||
onError: (err: unknown) => showToast(extractError(err), "error"),
|
||||
});
|
||||
|
||||
const assignOperatorMutation = useMutation({
|
||||
mutationFn: ({ side, userId }: { side: "red" | "blue"; userId: string | null }) =>
|
||||
assignTestOperators(testId!, side === "red" ? { red_tech_assignee: userId } : { blue_tech_assignee: userId }),
|
||||
onSuccess: () => {
|
||||
invalidateAll();
|
||||
showToast("Assignment updated", "success");
|
||||
},
|
||||
onError: (err: unknown) => showToast(extractError(err), "error"),
|
||||
});
|
||||
|
||||
// Evidence upload
|
||||
const uploadMutation = useMutation({
|
||||
mutationFn: ({ file, team }: { file: File; team: TeamSide }) =>
|
||||
@@ -532,6 +552,9 @@ export default function TestDetailPage() {
|
||||
isTogglingHold={holdMutation.isPending || resumeHoldMutation.isPending}
|
||||
onUpdateClassification={(value) => updateClassificationMutation.mutate(value)}
|
||||
isUpdatingClassification={updateClassificationMutation.isPending}
|
||||
operators={operators}
|
||||
onAssignOperator={(side, userId) => assignOperatorMutation.mutate({ side, userId })}
|
||||
isAssigningOperator={assignOperatorMutation.isPending}
|
||||
/>
|
||||
|
||||
{/* Content: Tabs + Sidebar */}
|
||||
|
||||
Reference in New Issue
Block a user