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:
@@ -184,12 +184,23 @@ function logParams(query: LogQuery, extra: Record<string, string | number | null
|
||||
|
||||
export type JobResult = { [key: string]: number | string | JobResult };
|
||||
|
||||
export interface SchedulerJob {
|
||||
id: string;
|
||||
interval_minutes: number;
|
||||
next_run: string | null;
|
||||
running: boolean;
|
||||
status: "ok" | "error" | "skipped" | null;
|
||||
/** How a run ended — the server's `RunStatus` (app/runstatus.py) minus `running`. `interrupted`:
|
||||
* the process stopped mid-run (a deploy, a crash), and its next start closed the row. */
|
||||
export type RunOutcome = "ok" | "error" | "skipped" | "interrupted";
|
||||
|
||||
/** One past run, as the row's history dots show it. */
|
||||
export interface RunBrief {
|
||||
id: number;
|
||||
status: RunOutcome | "running";
|
||||
trigger: "scheduled" | "manual" | "ad_hoc";
|
||||
started_at: string;
|
||||
finished_at: string | null;
|
||||
}
|
||||
|
||||
/** The fields every Scheduler row has from its runs: how the newest ended one went, and the few
|
||||
* before it. */
|
||||
export interface RunFields {
|
||||
status: RunOutcome | null;
|
||||
last_started: string | null;
|
||||
last_finished: string | null;
|
||||
last_result: string | null; // the English one-liner — the fallback for a job with no `result`
|
||||
@@ -197,16 +208,37 @@ export interface SchedulerJob {
|
||||
// named in the reader's language by lib/jobResult.
|
||||
result: JobResult | null;
|
||||
last_error: string | null;
|
||||
last_trigger: RunBrief["trigger"] | null;
|
||||
last_run_id: number | null;
|
||||
quota_units: number | null;
|
||||
/** The newest runs, newest first (at most three). */
|
||||
recent: RunBrief[];
|
||||
}
|
||||
|
||||
export interface SchedulerJob extends RunFields {
|
||||
id: string;
|
||||
interval_minutes: number;
|
||||
next_run: string | null;
|
||||
running: boolean;
|
||||
// Live progress while running (null when idle or for jobs that don't report it).
|
||||
// total is null for indeterminate progress (e.g. enrichment drains an unknown queue).
|
||||
progress: { current: number; total: number | null; phase: string | null } | null;
|
||||
}
|
||||
|
||||
/** Work a user starts on one target (a channel's full history): no interval, no "run now". */
|
||||
export interface SchedulerAdHoc extends RunFields {
|
||||
id: string;
|
||||
running: boolean;
|
||||
target: string | null;
|
||||
target_title: string | null;
|
||||
}
|
||||
|
||||
export interface SchedulerStatus {
|
||||
running_here: boolean;
|
||||
enabled: boolean;
|
||||
paused: boolean;
|
||||
jobs: SchedulerJob[];
|
||||
ad_hoc: SchedulerAdHoc[];
|
||||
queue: {
|
||||
channels_recent_pending: number;
|
||||
channels_deep_pending: number;
|
||||
|
||||
Reference in New Issue
Block a user