feat(admin): say what a queued restore is waiting for

A restore never runs beside a backup pass or another large job: the
heavy lane puts its delivery back in the queue for a minute at a time.
The job's own row says queued for the whole wait, and the console said
the restore starts "as soon as the background worker picks it up",
which read as a stuck worker while a backup ran.

The status routes now ask the queue. A delivery the lane put back sits
in created with a start_after ahead of now, and the active row of a
heavy queue is what holds the lane. The job view carries
waiting: "backup" | "busy" | null, and the console says it waits for
the running backup, or for another large job, and since when it has
been queued, in all nine locales. A queue that cannot be asked answers
no wait and is reported at warn.

The job view and its reads move to backup-restore-view.ts, which keeps
the worker module under its size baseline.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 21:43:55 +02:00
1 parent 6a87810dbf
commit 7936ab9a57
28 files changed
+544 -199

No files matched your search

@@ -26,7 +26,7 @@ import {
RestoreJobStatus,
visibleRestoreJobs,
} from "@/components/admin/backup-restore-status";
import type { BackupRestoreJobView } from "@/lib/jobs/backup-restore";
import type { BackupRestoreJobView } from "@/lib/jobs/backup-restore-view";
import { primeLocales } from "@/__tests__/helpers/prime-locales";
primeLocales("de");
@@ -54,6 +54,7 @@ function job(overrides: Partial<BackupRestoreJobView>): BackupRestoreJobView {
},
result: null,
failure: null,
waiting: null,
attempts: 1,
createdAt: "2026-09-25T08:00:00.000Z",
startedAt: "2026-09-25T08:00:01.000Z",
@@ -85,9 +86,29 @@ describe("RestoreJobStatus while it runs", () => {
expect(html).not.toContain('data-slot="restore-job-progress"');
});
it("says a queued restore is waiting", () => {
it("says a queued restore is waiting, and since when", () => {
const html = render(job({ status: "queued", phase: null, progress: null }));
expect(html).toContain("waiting to start");
expect(html).toContain("background worker picks it up");
expect(html).toContain("Queued at");
expect(html).not.toContain("data-waiting");
});
it("says it waits for the running backup, not for a stuck worker", () => {
const html = render(
job({ status: "queued", phase: null, progress: null, waiting: "backup" }),
);
expect(html).toContain('data-waiting="backup"');
expect(html).toContain("Waiting for the backup");
expect(html).not.toContain("background worker picks it up");
});
it("says it waits for another large job when that holds the server", () => {
const html = render(
job({ status: "queued", phase: null, progress: null, waiting: "busy" }),
);
expect(html).toContain('data-waiting="busy"');
expect(html).toContain("large job");
});
it("says when it was started again after a restart", () => {
@@ -179,4 +200,12 @@ describe("RestoreJobStatus in another language", () => {
expect(html).toContain("alex wird wiederhergestellt");
expect(html).toContain("Messwerte werden geschrieben");
});
it("says in German that it waits for the backup", () => {
const html = render(
job({ status: "queued", phase: null, progress: null, waiting: "backup" }),
"de",
);
expect(html).toContain("Wartet auf das Backup");
});
});