fix(api): prefilter Runs incident drilldown
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 1m34s
CD Pipeline / build-and-deploy (push) Successful in 5m10s
CD Pipeline / post-deploy-checks (push) Successful in 1m32s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-25 21:47:52 +08:00
parent 4e329bce24
commit d6d3f666a3
3 changed files with 129 additions and 0 deletions

View File

@@ -992,6 +992,16 @@ async def list_runs(
stmt = stmt.where(AwoooPRunState.project_id == project_id)
if state is not None:
stmt = stmt.where(AwoooPRunState.state == state)
if incident_id is not None:
incident_run_ids = await _find_run_ids_for_incident_filter(
db,
project_id=project_id,
incident_id=incident_id,
limit=max(per_page * 20, _MAX_LIST_CONTEXT_ROWS),
)
if not incident_run_ids:
return {"runs": [], "total": 0, "page": page, "per_page": per_page}
stmt = stmt.where(AwoooPRunState.run_id.in_(incident_run_ids))
offset = (page - 1) * per_page
if remediation_status or incident_id or callback_reply_status:
@@ -3696,6 +3706,92 @@ def _collect_run_incident_ids(
return incident_ids
async def _find_run_ids_for_incident_filter(
db: Any,
*,
project_id: str | None,
incident_id: str,
limit: int,
) -> list[UUID]:
"""Pre-filter run ids for a single incident before loading list context.
The old list filter loaded every run for a project and then searched message
sidecars in Python. That becomes too expensive on production-sized history
and can turn a single incident drilldown into a gateway timeout.
"""
params: dict[str, Any] = {
"incident_id": incident_id,
"incident_like": f"%{incident_id}%",
"limit": max(int(limit), 1),
}
project_run_where = ""
event_project_where = ""
outbound_project_where = ""
if project_id is not None:
params["project_id"] = project_id
project_run_where = "AND r.project_id = :project_id"
event_project_where = "AND e.project_id = :project_id"
outbound_project_where = "AND m.project_id = :project_id"
query = text(f"""
WITH matched AS (
SELECT r.run_id::text AS run_id, r.created_at AS ts
FROM awooop_run_state r
WHERE (
r.trigger_ref ILIKE :incident_like
OR r.error_detail ILIKE :incident_like
)
{project_run_where}
UNION ALL
SELECT e.run_id::text AS run_id, e.received_at AS ts
FROM awooop_conversation_event e
WHERE e.run_id IS NOT NULL
AND (
e.source_envelope #> '{{source_refs,incident_ids}}' ? :incident_id
OR e.content_preview ILIKE :incident_like
OR e.content_redacted ILIKE :incident_like
OR e.provider_event_id ILIKE :incident_like
)
{event_project_where}
UNION ALL
SELECT m.run_id::text AS run_id, COALESCE(m.sent_at, m.queued_at) AS ts
FROM awooop_outbound_message m
WHERE (
m.source_envelope #> '{{source_refs,incident_ids}}' ? :incident_id
OR m.source_envelope #> '{{awooop_status_chain,incident_ids}}' ? :incident_id
OR m.source_envelope #>> '{{awooop_status_chain,source_id}}' = :incident_id
OR m.source_envelope #>> '{{callback_reply,incident_id}}' = :incident_id
OR m.content_preview ILIKE :incident_like
OR m.content_redacted ILIKE :incident_like
OR m.send_error ILIKE :incident_like
)
{outbound_project_where}
)
SELECT run_id
FROM matched
WHERE run_id IS NOT NULL
GROUP BY run_id
ORDER BY MAX(ts) DESC
LIMIT :limit
""")
result = await db.execute(query, params)
run_ids: list[UUID] = []
for row in result.mappings().all():
raw_run_id = str(row.get("run_id") or "")
try:
run_id = uuid.UUID(raw_run_id)
except ValueError:
continue
if run_id not in run_ids:
run_ids.append(run_id)
return run_ids
async def _load_run_message_context(
db: Any,
runs: list[AwoooPRunState],