Auto-commit local changes before build (2026-01-16 12:56:34)
This commit is contained in:
parent
b46b7fbc21
commit
f780bbc399
@ -1 +1 @@
|
|||||||
v20260116-04-runchecks-autotask-ticket-polling
|
v20260116-05-autotask-ticket-create-link-all-open-runs
|
||||||
|
|||||||
@ -876,110 +876,6 @@ def run_checks_details():
|
|||||||
return jsonify({"status": "ok", "job": job_payload, "runs": runs_payload})
|
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")
|
@main_bp.post("/api/run-checks/autotask-ticket")
|
||||||
@login_required
|
@login_required
|
||||||
@roles_required("admin", "operator")
|
@roles_required("admin", "operator")
|
||||||
@ -1140,15 +1036,43 @@ def api_run_checks_create_autotask_ticket():
|
|||||||
except Exception:
|
except Exception:
|
||||||
ticket_number = None
|
ticket_number = None
|
||||||
|
|
||||||
try:
|
# Link the created Autotask ticket to all relevant open runs of the same job.
|
||||||
run.autotask_ticket_id = int(ticket_id)
|
# This matches the manual ticket workflow where one ticket remains visible across runs
|
||||||
except Exception:
|
# until it is explicitly resolved.
|
||||||
run.autotask_ticket_id = None
|
|
||||||
|
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
run.autotask_ticket_number = (str(ticket_number).strip() if ticket_number is not None else "") or None
|
|
||||||
run.autotask_ticket_created_at = now
|
linked_run_ids: list[int] = []
|
||||||
run.autotask_ticket_created_by_user_id = current_user.id
|
try:
|
||||||
|
open_runs = (
|
||||||
|
JobRun.query.filter(JobRun.job_id == run.job_id)
|
||||||
|
.filter(JobRun.reviewed_at.is_(None))
|
||||||
|
.order_by(JobRun.id.asc())
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
open_runs = [run]
|
||||||
|
|
||||||
|
# Safety: always include the explicitly selected run.
|
||||||
|
if run not in (open_runs or []):
|
||||||
|
open_runs = (open_runs or []) + [run]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
try:
|
||||||
|
r.autotask_ticket_id = int(ticket_id)
|
||||||
|
except Exception:
|
||||||
|
r.autotask_ticket_id = None
|
||||||
|
|
||||||
|
r.autotask_ticket_number = (str(ticket_number).strip() if ticket_number is not None else "") or None
|
||||||
|
r.autotask_ticket_created_at = now
|
||||||
|
r.autotask_ticket_created_by_user_id = current_user.id
|
||||||
|
|
||||||
|
if getattr(r, "id", None):
|
||||||
|
linked_run_ids.append(int(r.id))
|
||||||
|
|
||||||
# Also store an internal Ticket record and link it to the run.
|
# Also store an internal Ticket record and link it to the run.
|
||||||
# This keeps Tickets/Remarks, Job Details, and Run Checks indicators consistent with the existing manual workflow.
|
# This keeps Tickets/Remarks, Job Details, and Run Checks indicators consistent with the existing manual workflow.
|
||||||
@ -1188,13 +1112,14 @@ def api_run_checks_create_autotask_ticket():
|
|||||||
elif scope:
|
elif scope:
|
||||||
scope.resolved_at = None
|
scope.resolved_at = None
|
||||||
|
|
||||||
# Link ticket to this job run (idempotent)
|
# Link ticket to all relevant job runs (idempotent)
|
||||||
if internal_ticket and internal_ticket.id and run.id:
|
for rid in linked_run_ids or []:
|
||||||
if not TicketJobRun.query.filter_by(ticket_id=internal_ticket.id, job_run_id=run.id).first():
|
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=run.id, link_source="autotask"))
|
db.session.add(TicketJobRun(ticket_id=internal_ticket.id, job_run_id=rid, link_source="autotask"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.session.add(run)
|
for r in open_runs or []:
|
||||||
|
db.session.add(r)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
|
|||||||
@ -163,6 +163,15 @@ Changes:
|
|||||||
- Updated Run Checks UI to display polled PSA ticket status without modifying run state
|
- Updated Run Checks UI to display polled PSA ticket status without modifying run state
|
||||||
- Explicitly prevented any ticket mutation, resolution, or Backupchecks state changes
|
- Explicitly prevented any ticket mutation, resolution, or Backupchecks state changes
|
||||||
|
|
||||||
|
## v20260116-05-autotask-ticket-create-link-all-open-runs
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Fixed Autotask ticket creation to link the newly created ticket to all relevant open runs of the same job
|
||||||
|
- Aligned automatic ticket creation behaviour with existing manual ticket linking logic
|
||||||
|
- Ensured ticket linkage is applied consistently across runs until the ticket is resolved
|
||||||
|
- 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
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## v0.1.21
|
## v0.1.21
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user