fix(jira): store execution times as real UTC, fix RT date fields, label reopens
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

- execution_start_time/execution_end_time/detection_time/containment_time
  were captured from a datetime-local input with no local->UTC conversion,
  then stored/displayed as if already UTC — off by the browser's offset.
  Frontend now converts properly in both directions; backend schemas
  defensively normalize any tz-aware input to naive UTC as well.
- push_rt_submitted was pushing datetime.utcnow() (whenever the submit
  button was clicked) into Jira's RT Start/End Date fields instead of the
  operator-entered execution window. Now pushes the *first* round's
  execution start (recovered from round_history across reopens) and the
  *current* round's execution end. Removed push_rt_started, which only
  ever pushed a meaningless click-timestamp.
- Reopening a test now adds a "reopened" Jira label (read-modify-write so
  existing labels aren't clobbered), on top of the existing round-archived
  comment and status push.
This commit is contained in:
kitos
2026-07-13 14:57:42 +02:00
parent b48de743b6
commit 4221d2858b
5 changed files with 208 additions and 45 deletions
+24 -5
View File
@@ -44,9 +44,28 @@ import { createTemplate } from "../api/test-templates";
// ── Helpers ────────────────────────────────────────────────────────
// The backend always stores/returns naive UTC datetimes (no "Z", no
// offset). <input type="datetime-local"> shows/edits wall-clock time in
// the browser's own timezone, so both directions need an explicit
// UTC <-> local conversion — treating the raw digits as interchangeable
// silently shifts every value by the browser's UTC offset.
function isoToDatetimeLocal(iso: string | null): string {
if (!iso) return "";
return iso.replace("Z", "").replace(/\.\d+$/, "").slice(0, 16);
const utcStr = iso.endsWith("Z") || iso.includes("+") ? iso : iso + "Z";
const d = new Date(utcStr);
if (isNaN(d.getTime())) return "";
const pad = (n: number) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
function datetimeLocalToIso(local: string): string | undefined {
if (!local) return undefined;
// New Date() on a plain "YYYY-MM-DDTHH:mm" string (no zone designator)
// parses it as local time per the ECMA-262 date string spec — exactly
// the wall-clock time the user picked.
const d = new Date(local);
if (isNaN(d.getTime())) return undefined;
return d.toISOString();
}
// ── Page Component ─────────────────────────────────────────────────
@@ -205,8 +224,8 @@ export default function TestDetailPage() {
tool_used: redDraft.tool_used || undefined,
attack_success: redDraft.attack_success || undefined,
red_summary: redDraft.red_summary || undefined,
execution_start_time: redDraft.execution_start_time || undefined,
execution_end_time: redDraft.execution_end_time || undefined,
execution_start_time: datetimeLocalToIso(redDraft.execution_start_time),
execution_end_time: datetimeLocalToIso(redDraft.execution_end_time),
}),
onSuccess: () => {
invalidateAll();
@@ -220,8 +239,8 @@ export default function TestDetailPage() {
updateTestBlue(testId!, {
detection_result: (blueDraft.detection_result as TestResult) || undefined,
containment_result: (blueDraft.containment_result as ContainmentResult) || undefined,
detection_time: blueDraft.detection_time || undefined,
containment_time: blueDraft.containment_time || undefined,
detection_time: datetimeLocalToIso(blueDraft.detection_time),
containment_time: datetimeLocalToIso(blueDraft.containment_time),
blue_summary: blueDraft.blue_summary || undefined,
}),
onSuccess: () => {