Auto-commit local changes before build (2026-01-16 13:44:34)

This commit is contained in:
Ivo Oskamp 2026-01-16 13:44:34 +01:00
parent 25d1962f7b
commit ef8d12065b
3 changed files with 145 additions and 3 deletions

View File

@ -1 +1 @@
v20260116-07-autotask-ticket-link-all-runs-ticketjobrun-fix
v20260116-08-autotask-ticket-backfill-ticketjobrun

View File

@ -1000,15 +1000,149 @@ def api_run_checks_create_autotask_ticket():
run = JobRun.query.get(run_id)
if not run:
return jsonify({"status": "error", "message": "Run not found."}), 404
# Idempotent: if already created, return existing linkage.
# Idempotent: if already created, ensure internal Ticket/JobRun links exist and return existing linkage.
if getattr(run, "autotask_ticket_id", None):
linked_run_ids: list[int] = []
try:
rows = (
JobRun.query.filter(JobRun.job_id == run.job_id)
.filter(JobRun.reviewed_at.is_(None))
.with_entities(JobRun.id)
.order_by(JobRun.id.asc())
.all()
)
linked_run_ids = [int(rid) for (rid,) in rows if rid is not None]
except Exception:
linked_run_ids = []
# Safety: always include the explicitly selected run.
try:
if run.id and int(run.id) not in linked_run_ids:
linked_run_ids.append(int(run.id))
except Exception:
pass
# Best-effort: backfill internal Ticket + TicketScope + TicketJobRun links so Tickets/Remarks and Job Details stay consistent.
try:
ticket_code = (getattr(run, "autotask_ticket_number", None) or "").strip().upper()
if ticket_code:
job = Job.query.get(run.job_id)
customer = Customer.query.get(job.customer_id) if job and getattr(job, "customer_id", None) else None
# Compose a sensible title/description (best-effort).
subject = None
description = None
try:
status_display = run.status or "-"
try:
status_display, _, _, _ov_id, _ov_reason = _apply_overrides_to_run(job, run)
except Exception:
status_display = run.status or "-"
cname = getattr(customer, "name", None) or ""
jname = getattr(job, "job_name", None) or ""
subject = f"[Backupchecks] {cname} - {jname} - {status_display}"
settings = _get_or_create_settings()
msg = MailMessage.query.get(run.mail_message_id) if run.mail_message_id else None
overall_message = (getattr(msg, "overall_message", None) or "") if msg else ""
objects_payload: list[dict[str, str]] = []
try:
objs = run.objects.order_by(JobObject.object_name.asc()).all()
except Exception:
objs = list(run.objects or [])
for o in objs or []:
objects_payload.append(
{
"name": getattr(o, "object_name", "") or "",
"type": getattr(o, "object_type", "") or "",
"status": getattr(o, "status", "") or "",
"error_message": getattr(o, "error_message", "") or "",
}
)
if (not objects_payload) and msg:
try:
mos = MailObject.query.filter_by(mail_message_id=msg.id).order_by(MailObject.object_name.asc()).all()
except Exception:
mos = []
for mo in mos or []:
objects_payload.append(
{
"name": getattr(mo, "object_name", "") or "",
"type": getattr(mo, "object_type", "") or "",
"status": getattr(mo, "status", "") or "",
"error_message": getattr(mo, "error_message", "") or "",
}
)
description = _compose_autotask_ticket_description(
settings=settings,
job=job,
run=run,
status_display=status_display,
overall_message=overall_message,
objects_payload=objects_payload,
)
except Exception:
subject = subject or f"[Backupchecks] Autotask {ticket_code}"
description = description or ""
internal_ticket = Ticket.query.filter_by(ticket_code=ticket_code).first()
if not internal_ticket:
now = datetime.utcnow()
internal_ticket = Ticket(
ticket_code=ticket_code,
title=subject,
description=description or "",
active_from_date=_to_amsterdam_date(run.run_at) 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
# Ensure a job scope exists (used by popups / job details / tickets page)
scope = None
if job and job.id and internal_ticket and internal_ticket.id:
scope = TicketScope.query.filter_by(ticket_id=internal_ticket.id, scope_type="job", job_id=job.id).first()
if not scope and internal_ticket and internal_ticket.id:
scope = TicketScope(
ticket_id=internal_ticket.id,
scope_type="job",
customer_id=job.customer_id if job else None,
backup_software=job.backup_software if job else None,
backup_type=job.backup_type if job else None,
job_id=job.id if job else None,
job_name_match=job.job_name if job else None,
job_name_match_mode="exact",
resolved_at=None,
)
db.session.add(scope)
elif scope:
scope.resolved_at = None
# Link ticket to all relevant job runs (idempotent)
for rid in linked_run_ids or []:
if not TicketJobRun.query.filter_by(ticket_id=internal_ticket.id, job_run_id=rid).first():
db.session.add(TicketJobRun(ticket_id=internal_ticket.id, job_run_id=rid, link_source="autotask"))
db.session.commit()
except Exception:
db.session.rollback()
return jsonify(
{
"status": "ok",
"ticket_id": int(run.autotask_ticket_id),
"ticket_number": getattr(run, "autotask_ticket_number", None) or "",
"already_exists": True,
"linked_run_ids": linked_run_ids or [],
}
)

View File

@ -189,6 +189,14 @@ Changes:
- Made the list of runs to link deterministic by collecting run IDs first, then applying both run field updates and internal ticket linking across that stable set
- No changes made to polling logic or PSA status interpretation
## v20260116-08-autotask-ticket-backfill-ticketjobrun
- Fixed inconsistent ticket linking when creating Autotask tickets from the Run Checks page.
- Ensured that newly created Autotask tickets are linked to all related job runs, not only the selected run.
- Backfilled ticket-to-run associations so tickets appear correctly in the Tickets overview.
- 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.
***
## v0.1.21