feat(tests): archive round history on reopen, stop pausing from setting Jira On Hold
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
- Reopening a test for rework (red or blue) now archives the round that's ending into a new test_round_history table before resetting the live fields — procedure/results/detection data is preserved instead of overwritten, and Phase Timeline renders every past round alongside the current one, so Blue Team keeps visibility into earlier Red attempts. - Each archived round also posts a permanent Jira comment (push_round_archived) since Jira's custom fields only ever show the latest value once the next round resubmits — the comment thread is what preserves full history there. - push_pause_event no longer transitions the Jira issue to "On Hold" — a timer pause is not a real Hold, and flipping Jira status for it was misleading. Only the explicit Hold action (push_hold_event) does that now.
This commit is contained in:
@@ -1025,11 +1025,13 @@ def push_hold_event(
|
||||
|
||||
|
||||
def push_pause_event(db: Session, test, actor, *, resuming: bool = False) -> None:
|
||||
"""Post a timer pause/resume comment to Jira and toggle 'On Hold' / 'In Progress'.
|
||||
"""Post a timer pause/resume comment to Jira — status is left untouched.
|
||||
|
||||
Distinct from :func:`push_hold_event` — a short timer pause (the
|
||||
operator stepping away briefly) maps to "On Hold", while an explicit
|
||||
hold (something external blocking progress) maps to "Blocked".
|
||||
Distinct from :func:`push_hold_event` — a short timer pause (the timer
|
||||
sitting idle between rework rounds, or the operator briefly stepping
|
||||
away) is not the same thing as the test actually being blocked, so it
|
||||
must not flip the Jira issue to "On Hold". Only a genuine Hold action
|
||||
(:func:`push_hold_event`) does that. This just leaves a record.
|
||||
Non-fatal — any Jira error is logged and swallowed.
|
||||
"""
|
||||
if not has_admin_jira_configured(db):
|
||||
@@ -1052,16 +1054,8 @@ def push_pause_event(db: Session, test, actor, *, resuming: bool = False) -> Non
|
||||
|
||||
if resuming:
|
||||
comment = f"h3. ▶ Timer Resumed\n\n*Resumed by:* {actor.username}\n*At:* {ts}"
|
||||
try:
|
||||
jira.set_issue_status(link.jira_issue_key, "In Progress")
|
||||
except Exception as exc_t:
|
||||
logger.warning("Could not transition %s off On Hold: %s", link.jira_issue_key, exc_t)
|
||||
else:
|
||||
comment = f"h3. ⏸ Timer Paused\n\n*Paused by:* {actor.username}\n*At:* {ts}"
|
||||
try:
|
||||
jira.set_issue_status(link.jira_issue_key, "On Hold")
|
||||
except Exception as exc_t:
|
||||
logger.warning("Could not transition %s to On Hold: %s", link.jira_issue_key, exc_t)
|
||||
|
||||
jira.issue_add_comment(link.jira_issue_key, comment)
|
||||
link.last_synced_at = datetime.utcnow()
|
||||
@@ -1071,6 +1065,58 @@ def push_pause_event(db: Session, test, actor, *, resuming: bool = False) -> Non
|
||||
logger.warning("Failed to push pause event for test %s: %s", test.id, exc, exc_info=True)
|
||||
|
||||
|
||||
def push_round_archived(db: Session, test, actor, *, round_data) -> None:
|
||||
"""Post a permanent Jira comment summarizing a round right before it's
|
||||
reset for rework.
|
||||
|
||||
``round_data`` custom fields (attack success, detection result, etc.)
|
||||
only ever show Jira's *latest* value — each new round's submission
|
||||
overwrites the field. This comment is what preserves every prior
|
||||
round's results in Jira, since comments are never overwritten.
|
||||
Non-fatal — any Jira error is logged and swallowed.
|
||||
"""
|
||||
if not has_admin_jira_configured(db):
|
||||
return
|
||||
|
||||
link = (
|
||||
db.query(JiraLink)
|
||||
.filter(
|
||||
JiraLink.entity_type == JiraLinkEntityType.test,
|
||||
JiraLink.entity_id == test.id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not link:
|
||||
return
|
||||
|
||||
try:
|
||||
jira = get_admin_jira_client(db)
|
||||
team = round_data.team
|
||||
lines = [f"h3. \U0001F4CB Round {round_data.round_number} archived ({team} team)\n"]
|
||||
if team == "red":
|
||||
lines.append(f"*Procedure:* {round_data.procedure_text or '_none recorded_'}")
|
||||
lines.append(f"*Tool used:* {round_data.tool_used or '-'}")
|
||||
lines.append(f"*Attack success:* {round_data.attack_success.value if round_data.attack_success else '-'}")
|
||||
lines.append(f"*Summary:* {round_data.red_summary or '-'}")
|
||||
else:
|
||||
lines.append(f"*Detection result:* {round_data.detection_result.value if round_data.detection_result else '-'}")
|
||||
lines.append(f"*Containment result:* {round_data.containment_result.value if round_data.containment_result else '-'}")
|
||||
lines.append(f"*Summary:* {round_data.blue_summary or '-'}")
|
||||
lines.append(f"*Sent back by:* {actor.username}")
|
||||
lines.append(f"*Reopen notes:* {round_data.review_notes or '-'}")
|
||||
comment = "\n".join(lines)
|
||||
|
||||
jira.issue_add_comment(link.jira_issue_key, comment)
|
||||
link.last_synced_at = datetime.utcnow()
|
||||
db.flush()
|
||||
logger.info(
|
||||
"Posted round-archived comment to Jira %s (team=%s, round=%s)",
|
||||
link.jira_issue_key, team, round_data.round_number,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to push round-archived comment for test %s: %s", test.id, exc, exc_info=True)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user