feat(tests): on-hold button with reason modal, Jira comment + On Hold transition
Aegis CI / lint-and-test (push) Waiting to run
Snyk Security Scan / Python vulnerabilities (backend) (push) Waiting to run
Snyk Security Scan / npm vulnerabilities (frontend) (push) Waiting to run
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Waiting to run
Aegis CI / lint-and-test (push) Waiting to run
Snyk Security Scan / Python vulnerabilities (backend) (push) Waiting to run
Snyk Security Scan / npm vulnerabilities (frontend) (push) Waiting to run
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Waiting to run
This commit is contained in:
@@ -17,6 +17,8 @@ import {
|
||||
reopenTest,
|
||||
pauseTimer,
|
||||
resumeTimer,
|
||||
holdTest,
|
||||
resumeTest,
|
||||
getTestTimeline,
|
||||
getRetestChain,
|
||||
} from "../api/tests";
|
||||
@@ -48,6 +50,8 @@ export default function TestDetailPage() {
|
||||
}>({ open: false, side: "red" });
|
||||
|
||||
const [confirmReopen, setConfirmReopen] = useState(false);
|
||||
const [holdModal, setHoldModal] = useState(false);
|
||||
const [holdReason, setHoldReason] = useState("");
|
||||
|
||||
const [redDraft, setRedDraft] = useState({
|
||||
procedure_text: "",
|
||||
@@ -257,6 +261,26 @@ export default function TestDetailPage() {
|
||||
onError: (err: unknown) => showToast(extractError(err), "error"),
|
||||
});
|
||||
|
||||
const holdMutation = useMutation({
|
||||
mutationFn: (reason: string) => holdTest(testId!, reason),
|
||||
onSuccess: () => {
|
||||
invalidateAll();
|
||||
setHoldModal(false);
|
||||
setHoldReason("");
|
||||
showToast("Test placed on hold", "success");
|
||||
},
|
||||
onError: (err: unknown) => showToast(extractError(err), "error"),
|
||||
});
|
||||
|
||||
const resumeHoldMutation = useMutation({
|
||||
mutationFn: () => resumeTest(testId!),
|
||||
onSuccess: () => {
|
||||
invalidateAll();
|
||||
showToast("Test resumed", "success");
|
||||
},
|
||||
onError: (err: unknown) => showToast(extractError(err), "error"),
|
||||
});
|
||||
|
||||
// Evidence upload
|
||||
const uploadMutation = useMutation({
|
||||
mutationFn: ({ file, team }: { file: File; team: TeamSide }) =>
|
||||
@@ -394,6 +418,9 @@ export default function TestDetailPage() {
|
||||
onPauseTimer={() => pauseTimerMutation.mutate()}
|
||||
onResumeTimer={() => resumeTimerMutation.mutate()}
|
||||
isTogglingTimer={pauseTimerMutation.isPending || resumeTimerMutation.isPending}
|
||||
onHold={() => setHoldModal(true)}
|
||||
onResume={() => resumeHoldMutation.mutate()}
|
||||
isTogglingHold={holdMutation.isPending || resumeHoldMutation.isPending}
|
||||
/>
|
||||
|
||||
{/* Content: Tabs + Sidebar */}
|
||||
@@ -584,6 +611,49 @@ export default function TestDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* On Hold Modal */}
|
||||
{holdModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
|
||||
<div className="w-full max-w-md rounded-xl border border-amber-500/30 bg-gray-900 p-6 shadow-2xl">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-amber-300">Place Test On Hold</h2>
|
||||
<button
|
||||
onClick={() => { setHoldModal(false); setHoldReason(""); }}
|
||||
className="text-gray-400 hover:text-white transition-colors"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="mb-4 text-sm text-gray-400">
|
||||
Provide a reason for placing this test on hold. This will be sent as a comment to the linked Jira ticket and the ticket will be transitioned to <span className="text-amber-400 font-medium">On Hold</span>.
|
||||
</p>
|
||||
<textarea
|
||||
value={holdReason}
|
||||
onChange={(e) => setHoldReason(e.target.value)}
|
||||
placeholder="e.g. Assigned to another project until next sprint..."
|
||||
rows={4}
|
||||
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 placeholder-gray-500 focus:border-amber-500 focus:outline-none resize-none"
|
||||
/>
|
||||
<div className="mt-4 flex justify-end gap-3">
|
||||
<button
|
||||
onClick={() => { setHoldModal(false); setHoldReason(""); }}
|
||||
className="rounded-lg border border-gray-700 px-4 py-2 text-sm text-gray-300 hover:border-gray-600 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => holdMutation.mutate(holdReason)}
|
||||
disabled={!holdReason.trim() || holdMutation.isPending}
|
||||
className="flex items-center gap-1.5 rounded-lg bg-amber-600 px-4 py-2 text-sm font-medium text-white hover:bg-amber-500 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{holdMutation.isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
|
||||
Confirm On Hold
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Confirm Reopen Dialog */}
|
||||
<ConfirmDialog
|
||||
open={confirmReopen}
|
||||
|
||||
Reference in New Issue
Block a user