feat(tests): reopen rejected test keeps all content + rejection notes
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Backend (reopen_test): - Preserve red/blue validation NOTES — teams see exactly what to fix without losing the rejection context. Previously both notes were cleared. - Preserve all content fields: procedure_text, tool_used, red_summary, attack_success, blue_summary, detection_result (already the case). - Preserve evidences (separate table, unaffected — already the case). - Still clear: validation statuses + who/when validated (fresh re-validation required). Phase timing reset so the new execution starts clean. Frontend: - Button label: 'Reopen Test' → 'Continue Test' (more accurate intent) - Dialog title: 'Reopen Test' → 'Continue Test' - Dialog message: replaces alarming 'workflow will be restarted / clear all' with accurate description of what is preserved vs reset - Toast: explains what to do next
This commit is contained in:
@@ -655,27 +655,33 @@ def get_retest_chain(db: Session, test_id) -> list[Test]:
|
||||
|
||||
|
||||
def reopen_test(db: Session, test: Test, user: User) -> Test:
|
||||
"""Move a ``rejected`` test back to ``draft``, clearing validation fields.
|
||||
"""Move a ``rejected`` test back to ``draft`` for continued work.
|
||||
|
||||
This allows the teams to redo the test cycle.
|
||||
Preserves all content (procedure, summaries, evidences) and — crucially —
|
||||
the rejection NOTES so teams know what to fix without losing context.
|
||||
|
||||
Clears validation decisions (status, who validated, when) so leads must
|
||||
re-validate the updated submission. Phase timing is reset so the timer
|
||||
starts fresh for the new execution attempt.
|
||||
"""
|
||||
test = transition_state(
|
||||
db, test, TestState.draft, user,
|
||||
action_name="reopen_test",
|
||||
)
|
||||
|
||||
# Clear dual-validation fields
|
||||
# Clear validation DECISIONS — leads must re-validate the new attempt.
|
||||
# Rejection NOTES are intentionally kept so teams see what needs fixing.
|
||||
test.red_validation_status = None
|
||||
test.red_validated_by = None
|
||||
test.red_validated_at = None
|
||||
test.red_validation_notes = None
|
||||
# test.red_validation_notes → KEEP (rejection reason / clarification needed)
|
||||
|
||||
test.blue_validation_status = None
|
||||
test.blue_validated_by = None
|
||||
test.blue_validated_at = None
|
||||
test.blue_validation_notes = None
|
||||
# test.blue_validation_notes → KEEP (rejection reason / clarification needed)
|
||||
|
||||
# Clear phase timing fields
|
||||
# Reset phase timing so the new execution starts fresh
|
||||
test.red_started_at = None
|
||||
test.blue_started_at = None
|
||||
test.blue_work_started_at = None
|
||||
|
||||
@@ -225,7 +225,7 @@ export default function TestDetailHeader({
|
||||
className="flex items-center gap-1.5 rounded-lg border border-cyan-500/30 bg-cyan-900/20 px-4 py-2 text-sm font-medium text-cyan-400 hover:bg-cyan-900/40 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isTransitioning ? <Loader2 className="h-4 w-4 animate-spin" /> : <RotateCcw className="h-4 w-4" />}
|
||||
Reopen Test
|
||||
Continue Test
|
||||
</button>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ export default function TestDetailPage() {
|
||||
onSuccess: () => {
|
||||
invalidateAll();
|
||||
setConfirmReopen(false);
|
||||
showToast("Test reopened", "success");
|
||||
showToast("Test returned to Draft — address the feedback and resubmit", "success");
|
||||
},
|
||||
onError: (err: unknown) => {
|
||||
setConfirmReopen(false);
|
||||
@@ -587,9 +587,9 @@ export default function TestDetailPage() {
|
||||
{/* Confirm Reopen Dialog */}
|
||||
<ConfirmDialog
|
||||
open={confirmReopen}
|
||||
title="Reopen Test"
|
||||
message="This will move the test back to Draft state and clear all validation decisions. The Red/Blue workflow will need to be restarted. Are you sure?"
|
||||
confirmLabel="Reopen"
|
||||
title="Continue Test"
|
||||
message="The test will return to Draft so you can address the rejection feedback. All content is preserved — procedure, summaries, evidences and rejection notes. Only the validation decisions are cleared so leads can re-validate the updated submission."
|
||||
confirmLabel="Continue"
|
||||
variant="warning"
|
||||
isLoading={reopenMutation.isPending}
|
||||
onConfirm={() => reopenMutation.mutate()}
|
||||
|
||||
Reference in New Issue
Block a user