Auto-commit local changes before build (2026-01-16 14:13:31)
This commit is contained in:
parent
ef8d12065b
commit
9025d70b8e
@ -1 +1 @@
|
|||||||
v20260116-08-autotask-ticket-backfill-ticketjobrun
|
v20260116-09-autotask-ticket-propagate-active-runs
|
||||||
|
|||||||
@ -143,6 +143,148 @@ def _compose_autotask_ticket_description(
|
|||||||
# A run within +/- 1 hour of the inferred schedule time counts as fulfilling the slot.
|
# A run within +/- 1 hour of the inferred schedule time counts as fulfilling the slot.
|
||||||
MISSED_GRACE_WINDOW = timedelta(hours=1)
|
MISSED_GRACE_WINDOW = timedelta(hours=1)
|
||||||
|
|
||||||
|
def _propagate_autotask_ticket_to_active_runs(job_id: int) -> None:
|
||||||
|
"""Ensure Autotask ticket linkage is consistent across all active (unreviewed) runs of a job.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Only affects runs where reviewed_at is NULL (i.e. visible as active on Run Checks).
|
||||||
|
- If an active Autotask ticket exists on any run for this job, copy it to all other active runs.
|
||||||
|
- Keep internal Ticket + TicketScope + TicketJobRun relations in sync so Tickets/Remarks and Job Details stay consistent.
|
||||||
|
- Do nothing if no active ticket exists.
|
||||||
|
|
||||||
|
Best-effort: errors must never break page load.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Find a "source" run with an Autotask ticket among active runs.
|
||||||
|
source = (
|
||||||
|
JobRun.query.filter(JobRun.job_id == job_id)
|
||||||
|
.filter(JobRun.reviewed_at.is_(None))
|
||||||
|
.filter(JobRun.autotask_ticket_id.isnot(None))
|
||||||
|
.order_by(func.coalesce(JobRun.run_at, JobRun.created_at).desc(), JobRun.id.desc())
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if not source:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
ticket_id = int(getattr(source, "autotask_ticket_id", None) or 0)
|
||||||
|
except Exception:
|
||||||
|
ticket_id = 0
|
||||||
|
if ticket_id <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
ticket_number = (getattr(source, "autotask_ticket_number", None) or "").strip() or None
|
||||||
|
|
||||||
|
# Collect all active runs for this job.
|
||||||
|
active_runs = (
|
||||||
|
JobRun.query.filter(JobRun.job_id == job_id)
|
||||||
|
.filter(JobRun.reviewed_at.is_(None))
|
||||||
|
.order_by(func.coalesce(JobRun.run_at, JobRun.created_at).desc(), JobRun.id.desc())
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
if not active_runs:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update run fields (but never overwrite a different ticket).
|
||||||
|
changed = False
|
||||||
|
now = datetime.utcnow()
|
||||||
|
for r in active_runs:
|
||||||
|
existing = getattr(r, "autotask_ticket_id", None)
|
||||||
|
if existing is not None:
|
||||||
|
try:
|
||||||
|
if int(existing) != int(ticket_id):
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if getattr(r, "autotask_ticket_id", None) is None:
|
||||||
|
try:
|
||||||
|
r.autotask_ticket_id = int(ticket_id)
|
||||||
|
changed = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Always propagate ticket number if we have one and the run doesn't.
|
||||||
|
if ticket_number and not (getattr(r, "autotask_ticket_number", None) or "").strip():
|
||||||
|
r.autotask_ticket_number = ticket_number
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
# Preserve created-by metadata if already present; otherwise best-effort fill.
|
||||||
|
if getattr(r, "autotask_ticket_created_at", None) is None:
|
||||||
|
r.autotask_ticket_created_at = now
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
# Ensure internal ticket linkage when we have a ticket number.
|
||||||
|
if ticket_number:
|
||||||
|
ticket_code = ticket_number.strip().upper()
|
||||||
|
job = Job.query.get(job_id)
|
||||||
|
if job:
|
||||||
|
internal_ticket = Ticket.query.filter_by(ticket_code=ticket_code).first()
|
||||||
|
if not internal_ticket:
|
||||||
|
# Minimal record; title/description may be refined elsewhere.
|
||||||
|
internal_ticket = Ticket(
|
||||||
|
ticket_code=ticket_code,
|
||||||
|
title=f"[Backupchecks] Autotask {ticket_code}",
|
||||||
|
description="",
|
||||||
|
active_from_date=_to_amsterdam_date(getattr(source, "run_at", None)) or _to_amsterdam_date(now) or now.date(),
|
||||||
|
start_date=now,
|
||||||
|
resolved_at=None,
|
||||||
|
)
|
||||||
|
db.session.add(internal_ticket)
|
||||||
|
db.session.flush()
|
||||||
|
else:
|
||||||
|
# Keep it active if it was previously resolved globally.
|
||||||
|
if internal_ticket.resolved_at is not None:
|
||||||
|
internal_ticket.resolved_at = None
|
||||||
|
|
||||||
|
|
||||||
|
# If this ticket is already resolved for this job (or globally), do not propagate it to new runs.
|
||||||
|
if internal_ticket.resolved_at is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
existing_scope = TicketScope.query.filter_by(ticket_id=internal_ticket.id, scope_type="job", job_id=job.id).first()
|
||||||
|
if existing_scope and existing_scope.resolved_at is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ensure a job scope exists.
|
||||||
|
scope = existing_scope
|
||||||
|
if not scope:
|
||||||
|
scope = TicketScope(
|
||||||
|
ticket_id=internal_ticket.id,
|
||||||
|
scope_type="job",
|
||||||
|
customer_id=job.customer_id,
|
||||||
|
backup_software=job.backup_software,
|
||||||
|
backup_type=job.backup_type,
|
||||||
|
job_id=job.id,
|
||||||
|
job_name_match=job.job_name,
|
||||||
|
job_name_match_mode="exact",
|
||||||
|
resolved_at=None,
|
||||||
|
)
|
||||||
|
db.session.add(scope)
|
||||||
|
else:
|
||||||
|
scope.resolved_at = None
|
||||||
|
|
||||||
|
# Link ticket to all active job runs (idempotent).
|
||||||
|
for r in active_runs:
|
||||||
|
if not TicketJobRun.query.filter_by(ticket_id=internal_ticket.id, job_run_id=r.id).first():
|
||||||
|
db.session.add(TicketJobRun(ticket_id=internal_ticket.id, job_run_id=r.id, link_source="autotask"))
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
for r in active_runs:
|
||||||
|
db.session.add(r)
|
||||||
|
|
||||||
|
if changed or ticket_number:
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
db.session.rollback()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _status_is_success(status: str | None) -> bool:
|
def _status_is_success(status: str | None) -> bool:
|
||||||
s = (status or "").strip().lower()
|
s = (status or "").strip().lower()
|
||||||
@ -529,6 +671,27 @@ def run_checks_page():
|
|||||||
|
|
||||||
rows = q.limit(2000).all()
|
rows = q.limit(2000).all()
|
||||||
|
|
||||||
|
# Ensure Autotask tickets remain visible on all active runs until the ticket is resolved.
|
||||||
|
# This also guarantees that newly arrived runs inherit the existing ticket while it is still open.
|
||||||
|
try:
|
||||||
|
job_ids_for_page = [int(r.job_id) for r in rows]
|
||||||
|
if job_ids_for_page and not include_reviewed:
|
||||||
|
jobs_with_autotask = (
|
||||||
|
db.session.query(JobRun.job_id)
|
||||||
|
.filter(JobRun.job_id.in_(job_ids_for_page))
|
||||||
|
.filter(JobRun.reviewed_at.is_(None))
|
||||||
|
.filter(JobRun.autotask_ticket_id.isnot(None))
|
||||||
|
.group_by(JobRun.job_id)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
for (jid,) in jobs_with_autotask or []:
|
||||||
|
try:
|
||||||
|
_propagate_autotask_ticket_to_active_runs(int(jid))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Ensure override flags are up-to-date for the runs shown in this overview.
|
# Ensure override flags are up-to-date for the runs shown in this overview.
|
||||||
# The Run Checks modal computes override status on-the-fly, but the overview
|
# The Run Checks modal computes override status on-the-fly, but the overview
|
||||||
# aggregates by persisted JobRun.override_applied. Keep those flags aligned
|
# aggregates by persisted JobRun.override_applied. Keep those flags aligned
|
||||||
@ -731,6 +894,14 @@ def run_checks_details():
|
|||||||
|
|
||||||
runs = q.order_by(func.coalesce(JobRun.run_at, JobRun.created_at).desc(), JobRun.id.desc()).limit(400).all()
|
runs = q.order_by(func.coalesce(JobRun.run_at, JobRun.created_at).desc(), JobRun.id.desc()).limit(400).all()
|
||||||
|
|
||||||
|
# Ensure Autotask tickets are consistently linked to all active runs of this job (until resolved).
|
||||||
|
if not include_reviewed:
|
||||||
|
_propagate_autotask_ticket_to_active_runs(job.id)
|
||||||
|
# Re-load runs so the response reflects any propagated ticket fields immediately.
|
||||||
|
q2 = JobRun.query.filter(JobRun.job_id == job.id)
|
||||||
|
q2 = q2.filter(JobRun.reviewed_at.is_(None))
|
||||||
|
runs = q2.order_by(func.coalesce(JobRun.run_at, JobRun.created_at).desc(), JobRun.id.desc()).limit(400).all()
|
||||||
|
|
||||||
runs_payload = []
|
runs_payload = []
|
||||||
for run in runs:
|
for run in runs:
|
||||||
msg = MailMessage.query.get(run.mail_message_id) if run.mail_message_id else None
|
msg = MailMessage.query.get(run.mail_message_id) if run.mail_message_id else None
|
||||||
|
|||||||
@ -197,6 +197,14 @@ Changes:
|
|||||||
- Corrected Job Details visibility so open runs linked to the same ticket now display the ticket number consistently.
|
- Corrected Job Details visibility so open runs linked to the same ticket now display the ticket number consistently.
|
||||||
- Aligned Run Checks, Tickets, and Job Details views to use the same ticket-jobrun linkage logic.
|
- Aligned Run Checks, Tickets, and Job Details views to use the same ticket-jobrun linkage logic.
|
||||||
|
|
||||||
|
## v20260116-09-autotask-ticket-propagate-active-runs
|
||||||
|
|
||||||
|
- Updated ticket propagation logic so Autotask tickets are linked to all active job runs (non-Reviewed) visible on the Run Checks page.
|
||||||
|
- Ensured ticket remarks and ticket-jobrun entries are created for each active run, not only the initially selected run.
|
||||||
|
- Implemented automatic ticket inheritance for newly incoming runs of the same job while the ticket remains unresolved.
|
||||||
|
- Stopped ticket propagation once the ticket or job is marked as Resolved to prevent incorrect linking to closed incidents.
|
||||||
|
- Aligned Run Checks, Tickets overview, and Job Details to consistently reflect ticket presence across all active runs.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## v0.1.21
|
## v0.1.21
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user