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",
|
"Accept": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _request(
|
def _request_raw(
|
||||||
self,
|
self,
|
||||||
method: str,
|
method: str,
|
||||||
path: str,
|
path: str,
|
||||||
params: Optional[Dict[str, Any]] = None,
|
params: Optional[Dict[str, Any]] = None,
|
||||||
json_body: 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()
|
zone = self.get_zone_info()
|
||||||
base = zone.api_url.rstrip("/")
|
base = zone.api_url.rstrip("/")
|
||||||
url = f"{base}/v1.0/{path.lstrip('/')}"
|
url = f"{base}/v1.0/{path.lstrip('/')}"
|
||||||
headers = self._headers()
|
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)
|
h = dict(headers)
|
||||||
if extra_headers:
|
if extra_headers:
|
||||||
h.update(extra_headers)
|
h.update(extra_headers)
|
||||||
@ -149,8 +150,7 @@ class AutotaskClient:
|
|||||||
raise AutotaskError(
|
raise AutotaskError(
|
||||||
"Authentication failed (HTTP 401). "
|
"Authentication failed (HTTP 401). "
|
||||||
"Verify API Username, API Secret, and ApiIntegrationCode. "
|
"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,
|
status_code=401,
|
||||||
)
|
)
|
||||||
if resp.status_code == 403:
|
if resp.status_code == 403:
|
||||||
@ -163,6 +163,19 @@ class AutotaskClient:
|
|||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
raise AutotaskError(f"Autotask API error (HTTP {resp.status_code}).", status_code=resp.status_code)
|
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:
|
try:
|
||||||
return resp.json()
|
return resp.json()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@ -404,6 +417,15 @@ class AutotaskClient:
|
|||||||
"""
|
"""
|
||||||
return self._get_ticket_picklist_values(field_names=["status", "statusid"])
|
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]:
|
def create_ticket(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Create a Ticket in Autotask.
|
"""Create a Ticket in Autotask.
|
||||||
|
|
||||||
@ -413,20 +435,61 @@ class AutotaskClient:
|
|||||||
if not isinstance(payload, dict) or not payload:
|
if not isinstance(payload, dict) or not payload:
|
||||||
raise AutotaskError("Ticket payload is empty.")
|
raise AutotaskError("Ticket payload is empty.")
|
||||||
|
|
||||||
data = self._request("POST", "Tickets", json_body=payload)
|
resp = self._request_raw("POST", "Tickets", json_body=payload)
|
||||||
# Autotask commonly returns the created object or an items list.
|
|
||||||
|
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 isinstance(data, dict):
|
||||||
if "item" in data and isinstance(data.get("item"), dict):
|
for key in ("itemId", "itemID", "id", "ticketId", "ticketID"):
|
||||||
return data["item"]
|
if key in data and str(data.get(key) or "").isdigit():
|
||||||
if "items" in data and isinstance(data.get("items"), list) and data.get("items"):
|
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]
|
first = data.get("items")[0]
|
||||||
if isinstance(first, dict):
|
if isinstance(first, dict):
|
||||||
|
if "id" in first and str(first.get("id") or "").isdigit():
|
||||||
|
ticket_id = int(first["id"])
|
||||||
|
else:
|
||||||
return first
|
return first
|
||||||
if "id" in data:
|
|
||||||
return data
|
# Location header often contains the created entity URL.
|
||||||
# Fallback: return normalized first item if possible
|
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)
|
items = self._as_items_list(data)
|
||||||
if items:
|
if items:
|
||||||
return items[0]
|
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.
|
- 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.
|
- 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
|
## v0.1.21
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user