feat(dashboard_api): GET /webhook/inc-search route (ticket explorer)
Thin passthrough over reporting.fn_inc_search (fleettickets migration 13):
ad-hoc ticket lookup by ticket_id / owner / cluster / status / state / time for
the FleetOps ticket explorer. Mirrors /webhook/inc-dashboard (read-only conn,
jsonb passthrough); validates state in {open,closed,all} and ISO-8601 from/to.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1d4bd6000e
commit
e27933b1cb
1 changed files with 57 additions and 0 deletions
|
|
@ -368,6 +368,63 @@ def inc_dashboard(
|
|||
)
|
||||
|
||||
|
||||
# ── INC ticket explorer (search) ──────────────────────────────────────────────
|
||||
# Thin passthrough over reporting.fn_inc_search (fleettickets migration 13): ad-hoc
|
||||
# ticket lookup by id / engineer / cluster / status / state / time, for historical +
|
||||
# current tracking. Returns { count, truncated, limit, state, rows }.
|
||||
_INC_STATES = {"open", "closed", "all"}
|
||||
|
||||
|
||||
@app.get("/webhook/inc-search")
|
||||
def inc_search(
|
||||
ticket_id: str | None = None, # substring match on ticket_id
|
||||
owner: str | None = None, # engineer — case-insensitive substring on owner
|
||||
cluster: str | None = None, # exact tickets.inc.cluster
|
||||
status: str | None = None, # exact normalized_status
|
||||
state: str = "closed", # closed | open | all
|
||||
from_: str | None = Query(None, alias="from"), # closed-at range start (ISO-8601)
|
||||
to: str | None = None, # closed-at range end (exclusive, ISO-8601)
|
||||
):
|
||||
state = (state or "closed").lower()
|
||||
if state not in _INC_STATES:
|
||||
return _bad_request("state must be one of open|closed|all")
|
||||
f, t = _clean(from_), _clean(to)
|
||||
|
||||
def _parse(v):
|
||||
if not v:
|
||||
return None
|
||||
try:
|
||||
return datetime.fromisoformat(v)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
pf, pt = _parse(f), _parse(t)
|
||||
if pf is False or pt is False:
|
||||
return _bad_request("from/to must be ISO-8601 timestamps with an offset/Z")
|
||||
if pf and pt and pf >= pt:
|
||||
return _bad_request("from must be earlier than to")
|
||||
|
||||
try:
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT reporting.fn_inc_search(%s, %s, %s, %s, %s, %s, %s)",
|
||||
(_clean(ticket_id), _clean(owner), _clean(cluster), _clean(status), state, f, t),
|
||||
)
|
||||
payload = cur.fetchone()[0] or {}
|
||||
return JSONResponse(payload) # jsonb body returned unchanged
|
||||
except Exception:
|
||||
log.exception("inc-search failed")
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": {
|
||||
"type": "unknown",
|
||||
"message": "Ticket search is unavailable. Try again in a few seconds.",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ── Fleet trips (#002) ───────────────────────────────────────────────────────
|
||||
|
||||
_FILTER_OPTIONS_SQL = """
|
||||
|
|
|
|||
Loading…
Reference in a new issue