feat(scheduler): each row shows its last three runs, an interrupted run as such, and the ad-hoc runs beside the jobs

A row's one dot said how the LAST run went, and an interrupted run fell through to "idle" — a grey dot
that read as "no run recorded". The row now leads with its last three runs, newest first, each named
for a screen reader and in its tooltip (how it went, when, who started it); an interrupted run is a
ring, set apart from a skipped run and a failure, and its line says the server restarted under it.

A channel's full-history backfill gets its own "Ad-hoc runs" block: its runs, its channel and what
it fetched, no interval and no run button — it needs its target. AD_HOC_JOBS names every such job;
the backend-id contract holds their names in both languages, and run_ad_hoc refuses any other id.
This commit is contained in:
2026-10-18 01:20:53 +02:00
parent 27c2fcbd2e
commit 1b1e3cdcf2
9 changed files with 404 additions and 89 deletions
+34 -1
View File
@@ -13,7 +13,7 @@ import pytest
from sqlalchemy import select
from app import jobruns, logs, logsink, quota, scheduler, state
from app.models import AuditLog, JobRun, Notification, QuotaEvent
from app.models import AuditLog, Channel, JobRun, Notification, QuotaEvent
from app.runstatus import RunStatus, RunTrigger
_JOB = "unit_test_claim" # not a real job id — exercises the primitive without touching live jobs
@@ -335,10 +335,43 @@ def test_an_ad_hoc_run_is_the_users_run_of_its_target_with_its_spend_and_failure
assert (failed.status, failed.error) == ("error", "quota exceeded")
def test_the_page_shows_the_ad_hoc_runs_beside_the_jobs_with_their_channel(db, make_user):
user = make_user("shown@example.com")
db.add(Channel(id="UC_shown", title="BBC Earth"))
db.commit()
scheduler._ad_hoc("channel_backfill", lambda: {"skipped": "at capacity"}, user.id, "UC_none")
scheduler._ad_hoc("channel_backfill", lambda: {"videos_added": 69}, user.id, "UC_shown")
(row,) = scheduler.scheduler_snapshot(db)["ad_hoc"]
assert (row["id"], row["running"], row["target"], row["target_title"]) == (
"channel_backfill",
False,
"UC_shown",
"BBC Earth",
)
assert (row["status"], row["result"], row["last_trigger"]) == (
"ok",
{"videos_added": 69},
"ad_hoc",
)
assert len(row["recent"]) == 2
assert all(j["id"] != "channel_backfill" for j in scheduler.scheduler_snapshot(db)["jobs"])
jobruns.start("channel_backfill", RunTrigger.AD_HOC, user.id, "UC_shown")
assert scheduler.scheduler_snapshot(db)["ad_hoc"][0]["running"] is True
def _finished(db, job) -> list[JobRun]:
return [r for r in _runs(db, job) if r.status != "running"]
def test_an_ad_hoc_job_outside_the_named_list_is_refused(live_scheduler):
"""The page names every ad-hoc job from AD_HOC_JOBS; one outside it would show as a bare id."""
with pytest.raises(KeyError):
scheduler.run_ad_hoc("unnamed_job", dict, actor_id=1, target="x")
assert live_scheduler.get_jobs() == []
def test_a_second_ad_hoc_request_before_the_first_starts_is_that_same_run(
db, make_user, live_scheduler
):