fix(tests): stop requiring Containment Time when result is Not Contained
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
Containment Time was gated on detection alone, so picking "Not Contained" still showed the field and required it before saving — there's no containment moment to record when nothing was contained. Now only shown and required when the result is Contained or Partially Contained; a stale value from a prior Contained selection is cleared when the result changes away from it, so it can't get silently resubmitted once hidden.
This commit is contained in:
@@ -189,6 +189,12 @@ export default function TeamTabs({
|
||||
? blueDraft.detection_result === "detected" || blueDraft.detection_result === "partially_detected"
|
||||
: test.detection_result === "detected" || test.detection_result === "partially_detected";
|
||||
|
||||
// Containment Time only makes sense when something was actually contained —
|
||||
// "Not Contained" has no containment moment to record.
|
||||
const isContained = canEditBlue
|
||||
? blueDraft.containment_result === "contained" || blueDraft.containment_result === "partially_contained"
|
||||
: test.containment_result === "contained" || test.containment_result === "partially_contained";
|
||||
|
||||
// Hint messages shown to operators when editing is locked
|
||||
const redLockedHint =
|
||||
test.state === "draft" && role === "red_tech"
|
||||
@@ -520,7 +526,9 @@ export default function TeamTabs({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Detection & Containment times — only when detected/partially_detected */}
|
||||
{/* Detection & Containment times — Detection Time whenever detected;
|
||||
Containment Time only when something was actually contained —
|
||||
"Not Contained" has no containment moment to record. */}
|
||||
{isDetected && (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
@@ -541,24 +549,26 @@ export default function TeamTabs({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
||||
Containment Time (UTC) <span className="text-red-400">*</span>
|
||||
</label>
|
||||
{canEditBlue ? (
|
||||
<input
|
||||
type="datetime-local"
|
||||
required
|
||||
value={blueDraft.containment_time}
|
||||
onChange={(e) => onBlueFieldChange("containment_time", e.target.value)}
|
||||
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||
/>
|
||||
) : (
|
||||
<p className="text-sm text-gray-400">
|
||||
{test.containment_time ? new Date(test.containment_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{isContained && (
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
||||
Containment Time (UTC) <span className="text-red-400">*</span>
|
||||
</label>
|
||||
{canEditBlue ? (
|
||||
<input
|
||||
type="datetime-local"
|
||||
required
|
||||
value={blueDraft.containment_time}
|
||||
onChange={(e) => onBlueFieldChange("containment_time", e.target.value)}
|
||||
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||
/>
|
||||
) : (
|
||||
<p className="text-sm text-gray-400">
|
||||
{test.containment_time ? new Date(test.containment_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -446,7 +446,17 @@ export default function TestDetailPage() {
|
||||
};
|
||||
|
||||
const handleBlueFieldChange = (field: string, value: string) => {
|
||||
setBlueDraft((prev) => ({ ...prev, [field]: value }));
|
||||
setBlueDraft((prev) => {
|
||||
const next = { ...prev, [field]: value };
|
||||
// Containment Time only applies when something was actually
|
||||
// contained — clear a stale value left over from a previous
|
||||
// "Contained"/"Partially Contained" selection so it doesn't get
|
||||
// silently resubmitted once the field is hidden again.
|
||||
if (field === "containment_result" && value !== "contained" && value !== "partially_contained") {
|
||||
next.containment_time = "";
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleUploadEvidence = async (file: File, team: TeamSide) => {
|
||||
@@ -624,8 +634,9 @@ export default function TestDetailPage() {
|
||||
<button
|
||||
onClick={() => {
|
||||
const isDetected = blueDraft.detection_result === "detected" || blueDraft.detection_result === "partially_detected";
|
||||
const isContained = blueDraft.containment_result === "contained" || blueDraft.containment_result === "partially_contained";
|
||||
if (isDetected && !blueDraft.detection_time) { showToast("Detection Time is required", "error"); return; }
|
||||
if (isDetected && !blueDraft.containment_time) { showToast("Containment Time is required", "error"); return; }
|
||||
if (isContained && !blueDraft.containment_time) { showToast("Containment Time is required", "error"); return; }
|
||||
saveBlueMutation.mutate();
|
||||
}}
|
||||
disabled={saveBlueMutation.isPending}
|
||||
|
||||
Reference in New Issue
Block a user