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) ───────────────────────────────────────────────────────
|
# ── Fleet trips (#002) ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
_FILTER_OPTIONS_SQL = """
|
_FILTER_OPTIONS_SQL = """
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue