Auto-commit local changes before build (2026-01-15 16:17:26)
This commit is contained in:
parent
473044bd67
commit
66f5a57fe0
@ -1 +1 @@
|
||||
v20260115-15-autotask-default-ticket-status-setting
|
||||
v20260115-16-autotask-ticket-create-response-fix
|
||||
|
||||
@ -105,19 +105,20 @@ class AutotaskClient:
|
||||
"Accept": "application/json",
|
||||
}
|
||||
|
||||
def _request(
|
||||
def _request_raw(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
json_body: Optional[Dict[str, Any]] = None,
|
||||
) -> Any:
|
||||
) -> requests.Response:
|
||||
"""Perform an Autotask REST API request and return the raw response."""
|
||||
zone = self.get_zone_info()
|
||||
base = zone.api_url.rstrip("/")
|
||||
url = f"{base}/v1.0/{path.lstrip('/')}"
|
||||
headers = self._headers()
|
||||
|
||||
def do_request(use_basic_auth: bool, extra_headers: Optional[Dict[str, str]] = None):
|
||||
def do_request(use_basic_auth: bool, extra_headers: Optional[Dict[str, str]] = None) -> requests.Response:
|
||||
h = dict(headers)
|
||||
if extra_headers:
|
||||
h.update(extra_headers)
|
||||
@ -149,8 +150,7 @@ class AutotaskClient:
|
||||
raise AutotaskError(
|
||||
"Authentication failed (HTTP 401). "
|
||||
"Verify API Username, API Secret, and ApiIntegrationCode. "
|
||||
f"Environment={self.environment}, ZoneInfoBase={zi_base}, ZoneApiUrl={zone.api_url}."
|
||||
,
|
||||
f"Environment={self.environment}, ZoneInfoBase={zi_base}, ZoneApiUrl={zone.api_url}.",
|
||||
status_code=401,
|
||||
)
|
||||
if resp.status_code == 403:
|
||||
@ -163,6 +163,19 @@ class AutotaskClient:
|
||||
if resp.status_code >= 400:
|
||||
raise AutotaskError(f"Autotask API error (HTTP {resp.status_code}).", status_code=resp.status_code)
|
||||
|
||||
return resp
|
||||
|
||||
def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
json_body: Optional[Dict[str, Any]] = None,
|
||||
) -> Any:
|
||||
resp = self._request_raw(method=method, path=path, params=params, json_body=json_body)
|
||||
if not (resp.content or b""):
|
||||
return {}
|
||||
|
||||
try:
|
||||
return resp.json()
|
||||
except Exception as exc:
|
||||
@ -404,6 +417,15 @@ class AutotaskClient:
|
||||
"""
|
||||
return self._get_ticket_picklist_values(field_names=["status", "statusid"])
|
||||
|
||||
def get_ticket(self, ticket_id: int) -> Dict[str, Any]:
|
||||
"""Fetch a Ticket by ID via GET /Tickets/<id>."""
|
||||
if not isinstance(ticket_id, int) or ticket_id <= 0:
|
||||
raise AutotaskError("Invalid Autotask ticket id.")
|
||||
data = self._request("GET", f"Tickets/{ticket_id}")
|
||||
if isinstance(data, dict) and data:
|
||||
return data
|
||||
raise AutotaskError("Autotask did not return a ticket object.")
|
||||
|
||||
def create_ticket(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Create a Ticket in Autotask.
|
||||
|
||||
@ -413,20 +435,61 @@ class AutotaskClient:
|
||||
if not isinstance(payload, dict) or not payload:
|
||||
raise AutotaskError("Ticket payload is empty.")
|
||||
|
||||
data = self._request("POST", "Tickets", json_body=payload)
|
||||
# Autotask commonly returns the created object or an items list.
|
||||
resp = self._request_raw("POST", "Tickets", json_body=payload)
|
||||
|
||||
data: Any = {}
|
||||
if resp.content:
|
||||
try:
|
||||
data = resp.json()
|
||||
except Exception:
|
||||
# Some tenants return an empty body or a non-JSON body on successful POST.
|
||||
data = {}
|
||||
|
||||
ticket_id: Optional[int] = None
|
||||
|
||||
# Autotask may return a lightweight create result like {"itemId": 12345}.
|
||||
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"):
|
||||
for key in ("itemId", "itemID", "id", "ticketId", "ticketID"):
|
||||
if key in data and str(data.get(key) or "").isdigit():
|
||||
ticket_id = int(data[key])
|
||||
break
|
||||
|
||||
# Some variants wrap the created entity.
|
||||
if ticket_id is None and "item" in data and isinstance(data.get("item"), dict):
|
||||
item = data.get("item")
|
||||
if "id" in item and str(item.get("id") or "").isdigit():
|
||||
ticket_id = int(item["id"])
|
||||
else:
|
||||
return item
|
||||
|
||||
if ticket_id is None and "items" in data and isinstance(data.get("items"), list) and data.get("items"):
|
||||
first = data.get("items")[0]
|
||||
if isinstance(first, dict):
|
||||
if "id" in first and str(first.get("id") or "").isdigit():
|
||||
ticket_id = int(first["id"])
|
||||
else:
|
||||
return first
|
||||
if "id" in data:
|
||||
return data
|
||||
# Fallback: return normalized first item if possible
|
||||
|
||||
# Location header often contains the created entity URL.
|
||||
if ticket_id is None:
|
||||
location = (resp.headers.get("Location") or resp.headers.get("location") or "").strip()
|
||||
if location:
|
||||
try:
|
||||
last = location.rstrip("/").split("/")[-1]
|
||||
if last.isdigit():
|
||||
ticket_id = int(last)
|
||||
except Exception:
|
||||
ticket_id = None
|
||||
|
||||
# If we have an ID, fetch the full ticket object so callers can reliably access ticketNumber etc.
|
||||
if ticket_id is not None:
|
||||
return self.get_ticket(ticket_id)
|
||||
|
||||
# Last-resort fallback: normalize first item if possible.
|
||||
items = self._as_items_list(data)
|
||||
if items:
|
||||
return items[0]
|
||||
|
||||
raise AutotaskError("Autotask did not return a created ticket object.")
|
||||
raise AutotaskError(
|
||||
f"Autotask did not return a created ticket object (HTTP {resp.status_code})."
|
||||
)
|
||||
|
||||
@ -112,6 +112,11 @@ Changes:
|
||||
- Extended reference data refresh to include Ticket Statuses and updated diagnostics counters accordingly.
|
||||
- Added database column for cached ticket statuses and included it in migrations for existing installations.
|
||||
|
||||
## v20260115-16-autotask-ticket-create-response-fix
|
||||
- Fixed Autotask ticket creation handling for tenants that return a lightweight or empty POST /Tickets response.
|
||||
- Added support for extracting the created ticket ID from itemId/id fields and from the Location header.
|
||||
- Added a follow-up GET /Tickets/{id} to always retrieve the full created ticket object (ensuring ticketNumber/id are available).
|
||||
|
||||
***
|
||||
|
||||
## v0.1.21
|
||||
|
||||
Loading…
Reference in New Issue
Block a user