feat(activity): search/filter on both Activity-tab panes
CI / lint (push) Successful in 4s
CI / frontend-build (push) Successful in 21s
CI / backend-lint-and-test (push) Successful in 39s
CI / integration (push) Successful in 3m17s

Recent failures gains a client-side search over the already-loaded 24h
rows (task/queue/target/error), shown as a filtered/total count alongside
the existing error-type chips. All recent activity gains a debounced
server-side task-name search (new `task` ILIKE param on /runs) so it
spans the full history, not just the loaded page. LIKE wildcards are
escaped so task names' literal underscores match literally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 11:34:42 -04:00
parent 14c244bd3d
commit 70d4017cf6
4 changed files with 80 additions and 7 deletions
+13
View File
@@ -147,6 +147,7 @@ async def list_runs():
"""Paginated task_run history. Query params:
queue=<name> filter to one queue
status=<status> filter to one status (running/ok/error/timeout/retry)
task=<substr> case-insensitive substring match on task_name
limit=<int> default 50, max 200
before_id=<int> cursor for keyset pagination
@@ -161,6 +162,7 @@ async def list_runs():
queue = request.args.get("queue")
status = request.args.get("status")
task = request.args.get("task")
before_id_raw = request.args.get("before_id")
before_id = int(before_id_raw) if before_id_raw else None
@@ -170,6 +172,11 @@ async def list_runs():
stmt = stmt.where(TaskRun.queue == queue)
if status:
stmt = stmt.where(TaskRun.status == status)
if task:
# Task names contain literal underscores (download_source,
# vacuum_analyze) — escape LIKE wildcards so a search for
# "vacuum_analyze" doesn't treat "_" as a single-char match.
stmt = stmt.where(TaskRun.task_name.ilike(f"%{_escape_like(task)}%", escape="\\"))
if before_id is not None:
stmt = stmt.where(TaskRun.id < before_id)
stmt = stmt.limit(limit + 1)
@@ -225,6 +232,12 @@ async def list_failures():
})
def _escape_like(value: str) -> str:
"""Escape SQL LIKE/ILIKE metacharacters so user search text is matched
literally. Pairs with `escape="\\"` on the .ilike() call."""
return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
def _row_to_dict(r: TaskRun) -> dict:
return {
"id": r.id,