From dabec03f917bd68df202908d3a2ec9a2ec23adb9 Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Mon, 19 Jan 2026 11:51:58 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-01-19 11:51:58) --- .last-branch | 2 +- .../src/backend/app/main/routes_settings.py | 181 +++++++++++------- 2 files changed, 117 insertions(+), 66 deletions(-) diff --git a/.last-branch b/.last-branch index 371b3cc..a9c74c3 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260119-02-restoredto--v20260115-15-autotask-default-ticket-status-setting +v20260119-03-autotask-reference-data-auto-refresh diff --git a/containers/backupchecks/src/backend/app/main/routes_settings.py b/containers/backupchecks/src/backend/app/main/routes_settings.py index 2160fb5..96dba4d 100644 --- a/containers/backupchecks/src/backend/app/main/routes_settings.py +++ b/containers/backupchecks/src/backend/app/main/routes_settings.py @@ -407,6 +407,8 @@ def settings(): section = (request.args.get("section") or "general").strip().lower() or "general" if request.method == "POST": + autotask_form_touched = any(str(k).startswith("autotask_") for k in (request.form or {}).keys()) + # NOTE: The Settings UI has multiple tabs with separate forms. # Only update values that are present in the submitted form, to avoid # clearing unrelated settings when saving from another tab. @@ -563,6 +565,48 @@ def settings(): db.session.commit() flash("Settings have been saved.", "success") + # Autotask ticket defaults depend on reference data (queues, sources, statuses, priorities). + # When the Autotask integration is (re)configured, auto-refresh the cached reference data + # once so the dropdowns become usable immediately. + try: + if ( + autotask_form_touched + and bool(getattr(settings, "autotask_enabled", False)) + and bool(getattr(settings, "autotask_api_username", None)) + and bool(getattr(settings, "autotask_api_password", None)) + and bool(getattr(settings, "autotask_tracking_identifier", None)) + ): + missing_cache = ( + not bool(getattr(settings, "autotask_cached_queues_json", None)) + or not bool(getattr(settings, "autotask_cached_ticket_sources_json", None)) + or not bool(getattr(settings, "autotask_cached_ticket_statuses_json", None)) + or not bool(getattr(settings, "autotask_cached_priorities_json", None)) + ) + + if missing_cache: + queues, sources, statuses, pr_out = _refresh_autotask_reference_data(settings) + db.session.commit() + flash( + f"Autotask reference data refreshed. Queues: {len(queues)}. Ticket Sources: {len(sources)}. Ticket Statuses: {len(statuses)}. Priorities: {len(pr_out)}.", + "success", + ) + _log_admin_event( + "autotask_reference_data_auto_refreshed", + "Autotask reference data auto-refreshed after settings save.", + details=json.dumps({"queues": len(queues or []), "ticket_sources": len(sources or []), "ticket_statuses": len(statuses or []), "priorities": len(pr_out)}), + ) + except Exception as exc: + try: + db.session.rollback() + except Exception: + pass + flash(f"Autotask reference data refresh failed: {exc}", "warning") + _log_admin_event( + "autotask_reference_data_auto_refresh_failed", + "Autotask reference data auto-refresh failed after settings save.", + details=json.dumps({"error": str(exc)}), + ) + # If EML storage has been turned off, clear any stored blobs immediately. try: if getattr(settings, "ingest_eml_retention_days", 7) == 0: @@ -1308,6 +1352,77 @@ def settings_autotask_test_connection(): return redirect(url_for("main.settings", section="integrations")) +def _refresh_autotask_reference_data(settings): + """Refresh and persist Autotask reference data used for ticket default dropdowns.""" + from ..integrations.autotask.client import AutotaskClient + + client = AutotaskClient( + username=settings.autotask_api_username, + password=settings.autotask_api_password, + api_integration_code=settings.autotask_tracking_identifier, + environment=(settings.autotask_environment or "production"), + ) + + queues = client.get_queues() + sources = client.get_ticket_sources() + priorities = client.get_ticket_priorities() + statuses = client.get_ticket_statuses() + + # Store a minimal subset for dropdowns (id + name/label) + # Note: Some "reference" values are exposed as picklists (value/label) + # instead of entity collections (id/name). We normalize both shapes. + def _norm(items): + out = [] + for it in items or []: + if not isinstance(it, dict): + continue + _id = it.get("id") + if _id is None: + _id = it.get("value") + + name = ( + it.get("name") + or it.get("label") + or it.get("queueName") + or it.get("sourceName") + or it.get("description") + or "" + ) + try: + _id_int = int(_id) + except Exception: + continue + out.append({"id": _id_int, "name": str(name)}) + # Sort by name for stable dropdowns + out.sort(key=lambda x: (x.get("name") or "").lower()) + return out + + settings.autotask_cached_queues_json = json.dumps(_norm(queues)) + settings.autotask_cached_ticket_sources_json = json.dumps(_norm(sources)) + settings.autotask_cached_ticket_statuses_json = json.dumps(_norm(statuses)) + + # Priorities are returned as picklist values (value/label) + pr_out = [] + for it in priorities or []: + if not isinstance(it, dict): + continue + if it.get("isActive") is False: + continue + val = it.get("value") + label = it.get("label") or it.get("name") or "" + try: + val_int = int(val) + except Exception: + continue + pr_out.append({"id": val_int, "name": str(label)}) + pr_out.sort(key=lambda x: (x.get("name") or "").lower()) + + settings.autotask_cached_priorities_json = json.dumps(pr_out) + settings.autotask_reference_last_sync_at = datetime.utcnow() + + return queues, sources, statuses, pr_out + + @main_bp.route("/settings/autotask/refresh-reference-data", methods=["POST"]) @login_required @roles_required("admin") @@ -1319,71 +1434,7 @@ def settings_autotask_refresh_reference_data(): return redirect(url_for("main.settings", section="integrations")) try: - from ..integrations.autotask.client import AutotaskClient - client = AutotaskClient( - username=settings.autotask_api_username, - password=settings.autotask_api_password, - api_integration_code=settings.autotask_tracking_identifier, - environment=(settings.autotask_environment or "production"), - ) - - queues = client.get_queues() - sources = client.get_ticket_sources() - priorities = client.get_ticket_priorities() - statuses = client.get_ticket_statuses() - - # Store a minimal subset for dropdowns (id + name/label) - # Note: Some "reference" values are exposed as picklists (value/label) - # instead of entity collections (id/name). We normalize both shapes. - def _norm(items): - out = [] - for it in items or []: - if not isinstance(it, dict): - continue - _id = it.get("id") - if _id is None: - _id = it.get("value") - - name = ( - it.get("name") - or it.get("label") - or it.get("queueName") - or it.get("sourceName") - or it.get("description") - or "" - ) - try: - _id_int = int(_id) - except Exception: - continue - out.append({"id": _id_int, "name": str(name)}) - # Sort by name for stable dropdowns - out.sort(key=lambda x: (x.get("name") or "").lower()) - return out - - settings.autotask_cached_queues_json = json.dumps(_norm(queues)) - settings.autotask_cached_ticket_sources_json = json.dumps(_norm(sources)) - settings.autotask_cached_ticket_statuses_json = json.dumps(_norm(statuses)) - - # Priorities are returned as picklist values (value/label) - pr_out = [] - for it in priorities or []: - if not isinstance(it, dict): - continue - if it.get("isActive") is False: - continue - val = it.get("value") - label = it.get("label") or it.get("name") or "" - try: - val_int = int(val) - except Exception: - continue - pr_out.append({"id": val_int, "name": str(label)}) - pr_out.sort(key=lambda x: (x.get("name") or "").lower()) - - settings.autotask_cached_priorities_json = json.dumps(pr_out) - settings.autotask_reference_last_sync_at = datetime.utcnow() - + queues, sources, statuses, pr_out = _refresh_autotask_reference_data(settings) db.session.commit() flash(