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
+49 -9
View File
@@ -1107,6 +1107,7 @@ def push_round_archived(db: Session, test, actor, *, round_data) -> None:
comment = "\n".join(lines)
jira.issue_add_comment(link.jira_issue_key, comment)
_add_jira_label(jira, link.jira_issue_key, "reopened")
link.last_synced_at = datetime.utcnow()
db.flush()
logger.info(
@@ -1117,6 +1118,19 @@ def push_round_archived(db: Session, test, actor, *, round_data) -> None:
logger.warning("Failed to push round-archived comment for test %s: %s", test.id, exc, exc_info=True)
def _add_jira_label(jira, issue_key: str, label: str) -> None:
"""Add *label* to an issue's labels without clobbering the existing ones.
Jira's update API takes the whole ``labels`` array, so adding one means
read-modify-write: fetch the current list, append if missing, write it
back. Failures here are logged and swallowed by the caller.
"""
issue = jira.issue(issue_key, fields="labels")
current = (issue.get("fields") or {}).get("labels") or []
if label not in current:
jira.update_issue_field(issue_key, fields={"labels": current + [label]})
def push_bt_work_started(db: Session, test, actor) -> None:
"""Transition the Jira issue to "In Progress" when a blue tech picks up
a queued test to start evaluating.
@@ -1228,19 +1242,45 @@ def _update_test_fields(db: Session, test: Test, fields: dict) -> None:
)
def push_rt_started(db: Session, test: Test) -> None:
"""Set the RT Start Date field — called when the operator hits Start Execution."""
_update_test_fields(db, test, {
JIRA_FIELD_RT_START_DATE: datetime.utcnow().strftime("%Y-%m-%d"),
})
def push_rt_submitted(db: Session, test: Test) -> None:
"""Set the RT End Date + Attack Success fields — called when Red submits for review."""
fields: dict = {JIRA_FIELD_RT_END_DATE: datetime.utcnow().strftime("%Y-%m-%d")}
"""Set RT Start Date, RT End Date, and Attack Success — called when Red submits for review.
RT Start Date always reflects the *first* round's execution start (from
round_number=1 in round_history if this test has been reopened, else the
live field — this is round 1 in that case). RT End Date and Attack
Success always reflect the *current* round, since they're read live off
the test each time this runs and get pushed again on every resubmit.
Previously this pushed ``datetime.utcnow()`` (click time) instead of the
operator-entered execution window — fixed to read the real fields.
"""
from app.models.test_round_history import TestRoundHistory
fields: dict = {}
first_round = (
db.query(TestRoundHistory)
.filter(
TestRoundHistory.test_id == test.id,
TestRoundHistory.team == "red",
TestRoundHistory.round_number == 1,
)
.first()
)
first_start = (
first_round.execution_start_time
if first_round and first_round.execution_start_time
else test.execution_start_time
)
if first_start:
fields[JIRA_FIELD_RT_START_DATE] = first_start.strftime("%Y-%m-%d")
if test.execution_end_time:
fields[JIRA_FIELD_RT_END_DATE] = test.execution_end_time.strftime("%Y-%m-%d")
attack_success = _enum_value(test.attack_success)
if attack_success in _ATTACK_SUCCESS_TO_JIRA:
fields[JIRA_FIELD_ATTACK_SUCCESS] = _select_field(_ATTACK_SUCCESS_TO_JIRA[attack_success])
_update_test_fields(db, test, fields)