From ddc6eaa12a3ce20d0b46a1c7d54b955d796b792b Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Tue, 3 Feb 2026 13:59:08 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-02-03 13:59:08) --- .last-branch | 2 +- .../app/integrations/autotask/client.py | 79 +++++++++++++------ .../src/backend/app/main/routes_run_checks.py | 6 +- docs/changelog.md | 6 ++ 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/.last-branch b/.last-branch index 40b0957..88cd114 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260203-04-autotask-resolve-user-note +v20260203-06-autotask-ticketnotes-child-endpoint diff --git a/containers/backupchecks/src/backend/app/integrations/autotask/client.py b/containers/backupchecks/src/backend/app/integrations/autotask/client.py index 114beb3..494b231 100644 --- a/containers/backupchecks/src/backend/app/integrations/autotask/client.py +++ b/containers/backupchecks/src/backend/app/integrations/autotask/client.py @@ -522,38 +522,67 @@ class AutotaskClient: def create_ticket_note(self, note_payload: Dict[str, Any]) -> Dict[str, Any]: - """Create a TicketNote via POST /TicketNotes. + """Create a TicketNote for a Ticket. - Note: Tenant support varies. Callers should handle AutotaskError(status_code=404) - and implement a fallback if needed. - """ + Autotask TicketNotes are a child collection of Tickets. In some tenants, creating notes via the + root entity endpoint (POST /TicketNotes) is not supported, while creating via the parent ticket + child URL may work (POST /Tickets/{id}/TicketNotes). - if not isinstance(note_payload, dict): - raise AutotaskError("Invalid ticket note payload.") + Callers can keep a fallback (for example updating the Ticket description) if both routes fail. + """ + if not isinstance(note_payload, dict): + raise AutotaskError("Invalid ticket note payload.") + + try: + tid = int(note_payload.get("ticketID") or note_payload.get("ticketId") or 0) + except Exception: + tid = 0 + if tid <= 0: + raise AutotaskError("Invalid ticketID in ticket note payload.") + + title = str(note_payload.get("title") or "Backupchecks") + description = str( + note_payload.get("description") + or note_payload.get("note") + or note_payload.get("body") + or "" + ) + + pub_val = note_payload.get("publish") + # REST uses an integer picklist; in practice '1' corresponds to "ALL" / all Autotask users. + if isinstance(pub_val, bool): + publish = 1 if pub_val else 1 + else: try: - tid = int(note_payload.get("ticketID") or note_payload.get("ticketId") or 0) + publish = int(pub_val) if pub_val is not None else 1 except Exception: - tid = 0 - if tid <= 0: - raise AutotaskError("Invalid ticketID in ticket note payload.") + publish = 1 - data = self._request("POST", "TicketNotes", json_body=note_payload) - if isinstance(data, dict): - if "item" in data and isinstance(data.get("item"), dict): - return data["item"] - if "items" in data and isinstance(data.get("items"), list) and data.get("items"): - first = data.get("items")[0] - if isinstance(first, dict): - return first - if "id" in data: - return data + child_payload = { + "title": title, + "description": description, + "publish": publish, + } - items = self._as_items_list(data) - if items: - return items[0] + # Preferred: parent-child URL + data = self._request("POST", f"Tickets/{tid}/TicketNotes", json_body=child_payload) - raise AutotaskError("Autotask did not return a created ticket note object.") + if isinstance(data, dict): + if "item" in data and isinstance(data.get("item"), dict): + return data["item"] + if "items" in data and isinstance(data.get("items"), list) and data.get("items"): + first = data.get("items")[0] + if isinstance(first, dict): + return first + if "id" in data: + return data + + items = self._as_items_list(data) + if items: + return items[0] + + raise AutotaskError("Autotask did not return a created ticket note object.") def get_ticket_note(self, note_id: int) -> Dict[str, Any]: """Retrieve a TicketNote by ID via GET /TicketNotes/{id}.""" @@ -773,4 +802,4 @@ class AutotaskClient: # Respect limit if tenant returns more. if limit and isinstance(limit, int) and limit > 0: return items[: int(limit)] - return items + return items \ No newline at end of file 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 6454626..036b276 100644 --- a/containers/backupchecks/src/backend/app/main/routes_run_checks.py +++ b/containers/backupchecks/src/backend/app/main/routes_run_checks.py @@ -1869,8 +1869,8 @@ def api_run_checks_autotask_resolve_note(): note_payload = { "ticketID": ticket_id, "title": "Backupchecks", - "note": body, - "publish": True, + "description": body, + "publish": 1, } created = client.create_ticket_note(note_payload) @@ -2237,4 +2237,4 @@ def api_run_checks_mark_success_override(): except Exception: pass - return jsonify({"status": "ok", "message": "Override created."}) + return jsonify({"status": "ok", "message": "Override created."}) \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index 593c4f4..11e0491 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -487,6 +487,12 @@ Changes: - Updated backend validation to only return success when the TicketNote is successfully created. - Aligned frontend success messaging with actual TicketNote creation in Autotask. +## v20260203-06-autotask-ticketnotes-child-endpoint + +- Updated the Resolve action to create ticket notes using the Autotask child endpoint POST /Tickets/{TicketID}/Notes. +- Removed usage of the unsupported POST /TicketNotes endpoint. +- Ensured the created note is user-visible in Autotask and clearly marks the ticket as resolved by Backupchecks. + *** ## v0.1.21