fix(activity): an ad-hoc run's row is one run's, a rollback keeps a run whole, Back reads one entry's filters

- The channel-backfill row says "running" from the runs this process has open, not from the table:
  a row whose outcome failed to write no longer reads as running until a restart. The run still
  going leads the row, so a newer run's outcome never sits under the older one's channel.
- Migration 0109's downgrade carries every run's trigger, spend and start in the audit row, and the
  upgrade restores them: a manual run whose admin was deleted stays manual.
- Back/Forward is read by the page's one popstate listener, both halves in one render; the server
  log adopts its half during that render, so no read goes out with one entry's filters and the
  other's range.
- The server log's failed facets use the other tabs' words (the menus may be short); the accounts
  message shows only when the menu really lacks them.
This commit is contained in:
2026-10-18 17:32:22 +02:00
parent fad656988a
commit 13b7577d9f
12 changed files with 188 additions and 72 deletions
+13 -11
View File
@@ -13,7 +13,7 @@ log writer runs behind the event).
Runs are not admin actions, so the audit log stops carrying them: the existing `job_run` audit rows
MOVE into `job_runs` (start = end = when the row was written, the only time it kept), and leave the
audit log. The downgrade moves every run back into the audit log as such a row (one still running as
interrupted, a run's trigger and target in its `after`), then drops the table.
interrupted, what the audit row has no column for in its `after`), then drops the table.
"""
import sqlalchemy as sa
@@ -72,25 +72,27 @@ def upgrade() -> None:
)
# The audit trail's runs move over: the three statuses the scheduler wrote, and `interrupted`,
# which a downgrade writes back (as it does a run's `trigger` and `target`, which the audit row
# has no column for), so a rollback and a redeploy keep every run. A status the run table does
# which a downgrade writes back (as it does a run's trigger, target, spend and start, which the
# audit row has no column for: its actor alone cannot say "manual" once that admin is deleted),
# so a rollback and a redeploy keep every run as it was. A status the run table does
# not know would fail the CHECK.
op.execute(
"""
INSERT INTO job_runs (job, trigger, actor_id, target, status, started_at, finished_at,
summary, result, error, failure)
summary, result, error, failure, quota_units)
SELECT target_id,
coalesce(after::jsonb ->> 'trigger',
CASE WHEN actor_id IS NULL THEN 'scheduled' ELSE 'manual' END),
actor_id,
after::jsonb ->> 'target',
after::jsonb ->> 'status',
created_at,
coalesce((after::jsonb ->> 'started_at')::timestamptz, created_at),
created_at,
CASE WHEN after::jsonb ->> 'status' <> 'error' THEN after::jsonb ->> 'summary' END,
CASE WHEN after::jsonb ->> 'status' <> 'error' THEN after::jsonb -> 'result' END,
CASE WHEN after::jsonb ->> 'status' = 'error' THEN after::jsonb ->> 'summary' END,
CASE WHEN after::jsonb ->> 'status' = 'error' THEN after::jsonb -> 'result' END
CASE WHEN after::jsonb ->> 'status' = 'error' THEN after::jsonb -> 'result' END,
coalesce((after::jsonb ->> 'quota_units')::int, 0)
FROM audit_log
WHERE action = 'job_run'
AND target_id IS NOT NULL
@@ -109,9 +111,9 @@ def upgrade() -> None:
def downgrade() -> None:
# A run still `running` is one the rollback stopped: it goes back as interrupted. Its trigger and
# target ride in `after` when the audit row's own columns cannot say them (an ad-hoc run of one
# channel), so the upgrade restores them.
# A run still `running` is one the rollback stopped: it goes back as interrupted. What the audit
# row's own columns cannot say rides in `after` (its trigger, target, spend and start), so the
# upgrade restores them.
op.execute(
"""
INSERT INTO audit_log (actor_id, action, target_type, target_id, summary, after, created_at)
@@ -132,8 +134,8 @@ def downgrade() -> None:
THEN jsonb_build_object('result', result)
ELSE '{}'::jsonb
END
|| CASE WHEN trigger = 'ad_hoc'
THEN jsonb_build_object('trigger', trigger) ELSE '{}'::jsonb END
|| jsonb_build_object('trigger', trigger, 'quota_units', quota_units,
'started_at', started_at)
|| CASE WHEN target IS NOT NULL
THEN jsonb_build_object('target', target) ELSE '{}'::jsonb END)::json,
coalesce(finished_at, started_at)