From 487f923064eb36a442ca9cc88931ba8580b5c5ba Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Fri, 16 Jan 2026 13:17:06 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-01-16 13:17:06) --- .last-branch | 2 +- .../src/backend/app/main/routes_run_checks.py | 112 +++++++++++++++++- docs/changelog.md | 8 ++ 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/.last-branch b/.last-branch index 82dca39..071f344 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260116-05-autotask-ticket-create-link-all-open-runs +v20260116-06-runchecks-polling-merge-fix diff --git a/containers/backupchecks/src/backend/app/main/routes_run_checks.py b/containers/backupchecks/src/backend/app/main/routes_run_checks.py index c566f54..0c24fdb 100644 --- a/containers/backupchecks/src/backend/app/main/routes_run_checks.py +++ b/containers/backupchecks/src/backend/app/main/routes_run_checks.py @@ -876,6 +876,110 @@ def run_checks_details(): return jsonify({"status": "ok", "job": job_payload, "runs": runs_payload}) + + +@main_bp.get("/api/run-checks/autotask-ticket-poll") +@login_required +@roles_required("admin", "operator") +def api_run_checks_autotask_ticket_poll(): + """Read-only polling of Autotask ticket state for Run Checks. + + Important: + - No Backupchecks state is modified. + - No mutations are performed in Autotask. + - This endpoint is intended to be called only from the Run Checks page. + """ + + include_reviewed = False + if get_active_role() == "admin": + include_reviewed = request.args.get("include_reviewed", "0") in ("1", "true", "yes", "on") + + # Only consider recently relevant runs to keep the payload small. + # We intentionally avoid unbounded history polling. + days = 60 + try: + days = int(request.args.get("days", "60")) + except Exception: + days = 60 + if days < 1: + days = 1 + if days > 180: + days = 180 + + now_utc = datetime.utcnow().replace(tzinfo=None) + window_start = now_utc - timedelta(days=days) + + q = JobRun.query.filter(JobRun.autotask_ticket_id.isnot(None)) + if not include_reviewed: + q = q.filter(JobRun.reviewed_at.is_(None)) + + # Only poll runs in our time window. + q = q.filter(func.coalesce(JobRun.run_at, JobRun.created_at) >= window_start) + + runs = ( + q.order_by(func.coalesce(JobRun.run_at, JobRun.created_at).desc(), JobRun.id.desc()) + .limit(400) + .all() + ) + + ticket_ids = [] + seen = set() + for r in runs: + tid = getattr(r, "autotask_ticket_id", None) + try: + tid_int = int(tid) + except Exception: + continue + if tid_int <= 0 or tid_int in seen: + continue + seen.add(tid_int) + ticket_ids.append(tid_int) + + if not ticket_ids: + return jsonify({"status": "ok", "tickets": []}) + + # If integration is disabled, do not fail the page. + settings = _get_or_create_settings() + if not getattr(settings, "autotask_enabled", False): + return jsonify({"status": "ok", "tickets": [], "autotask_enabled": False}) + + try: + client = _build_autotask_client_from_settings() + except Exception as exc: + return jsonify({"status": "ok", "tickets": [], "autotask_enabled": True, "message": str(exc)}) + + corr_id = datetime.utcnow().strftime("rcpoll-%Y%m%d%H%M%S") + + # Query tickets in Autotask (best-effort) + tickets = [] + try: + tickets = client.query_tickets_by_ids(ticket_ids, corr_id=corr_id) + except Exception: + tickets = [] + + # Build a minimal payload for UI use. + out = [] + for t in tickets or []: + if not isinstance(t, dict): + continue + tid = t.get("id") + try: + tid_int = int(tid) + except Exception: + continue + + out.append( + { + "id": tid_int, + "ticketNumber": (t.get("ticketNumber") or t.get("TicketNumber") or "") or "", + "status": t.get("status"), + "statusName": (t.get("statusName") or t.get("StatusName") or "") or "", + "title": (t.get("title") or t.get("Title") or "") or "", + "lastActivityDate": (t.get("lastActivityDate") or t.get("LastActivityDate") or t.get("lastActivity") or "") or "", + } + ) + + return jsonify({"status": "ok", "tickets": out, "autotask_enabled": True}) @main_bp.post("/api/run-checks/autotask-ticket") @login_required @roles_required("admin", "operator") @@ -1059,8 +1163,12 @@ def api_run_checks_create_autotask_ticket(): for r in open_runs or []: # Do not overwrite an existing (different) ticket linkage. existing_id = getattr(r, "autotask_ticket_id", None) - if existing_id and int(existing_id) != int(ticket_id): - continue + if existing_id: + try: + if int(existing_id) != int(ticket_id): + continue + except Exception: + continue try: r.autotask_ticket_id = int(ticket_id) diff --git a/docs/changelog.md b/docs/changelog.md index a13b157..a1e0c70 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -172,6 +172,14 @@ Changes: - Prevented Phase 2.1 polling from being blocked by incomplete ticket-run associations - No changes made to polling logic, resolution logic, or PSA state interpretation +## v20260116-06-runchecks-polling-merge-fix + +### Changes: +- Restored Phase 2.1 read-only Autotask polling logic after ticket-creation fix overwrote Run Checks routes +- Merged polling endpoint and UI polling trigger with updated ticket-linking behaviour +- Ensured polled PSA ticket status is available again on the Run Checks page +- No changes made to ticket creation logic, resolution handling, or Backupchecks run state + *** ## v0.1.21