fix(activity): runs keep their true outcome and spend, the page reaches every kept row, a closing dialog leaves the server log alone
Backend - A run's outcome write sits outside the job's own try: a database blip after a run that succeeded no longer records it as failed (the row waits for the next start's sweep). - An interrupted run keeps what it spent (the sweep sums its quota rows, as finish does). - The Scheduler's ad-hoc row says running while any backfill runs, not only the newest. - recent_runs reads each job's last runs off its index instead of ranking the whole table. - An interval change is stored with its audit row in one transaction, then rescheduled. - One API instance per database is the stated contract (.env.example, jobruns). - Turning full history on for a channel already complete records no run. - Migration 0109 round-trips interrupted and ad-hoc runs; the run words' DB CHECKs are test-held to the enums; the account filter is one helper for the four activity reads. - An audit row says how many of its request's log lines are still kept. Frontend - An "All" period reaches every row still kept (runs and admin actions outlive 90 days). - Back/Forward is followed in place by the server log: a closing dialog no longer turns Live off; a pivot still opens a fresh log. - Runs moved over from the audit log read their one-liner in the reader's language. - Filter choices or accounts that failed to load say so beside the filters, with a retry. - An old server-log link keeps its account, text and range; a remembered retired page opens its tab. - A request whose lines are gone says so instead of linking to an empty list. - The log lines and the history rows share one row frame, and the server log one list frame with the other tabs; an empty list is announced; each day's quota is readable.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { RUN_STATUSES } from "../lib/activityUrlState";
|
||||
import { vocabKey } from "../lib/vocab";
|
||||
|
||||
// Every id the BACKEND owns and the UI names must have its name in both languages. The UI looks these
|
||||
@@ -72,6 +74,15 @@ const jobs = [
|
||||
const enumValues = (file: string) =>
|
||||
[...backend(file).matchAll(/^\s+[A-Z_]+ = "(\w+)"$/gm)].map((m) => m[1]!);
|
||||
const auditActions = enumValues("audit.py");
|
||||
// A background run's words (backend/app/runstatus.py): its status and what started it.
|
||||
const runEnum = (name: string) =>
|
||||
[
|
||||
...(
|
||||
backend("runstatus.py").split(`class ${name}(StrEnum):`)[1]?.split("\nclass ")[0] ?? ""
|
||||
).matchAll(/^\s+[A-Z_]+ = "(\w+)"$/gm),
|
||||
].map((m) => m[1]!);
|
||||
const runStatuses = runEnum("RunStatus");
|
||||
const runTriggers = runEnum("RunTrigger");
|
||||
const quotaActions = enumValues("quota.py");
|
||||
// The admin actions' refusal reasons → `errors.admin.<reason>`. The codes are read from
|
||||
// `localizeDetail`'s own ADMIN_REFUSALS, so a code added there is scanned here too; a detail dict
|
||||
@@ -260,6 +271,8 @@ describe("backend ids have their names in both languages", () => {
|
||||
expect(jobs).toEqual(expect.arrayContaining(["rss_poll", "audit_gc"]));
|
||||
expect(auditActions).toEqual(expect.arrayContaining(["config_set", "audit_clear"]));
|
||||
expect(quotaActions.length).toBeGreaterThan(0);
|
||||
expect(runStatuses).toEqual(expect.arrayContaining(["running", "interrupted"]));
|
||||
expect(runTriggers).toEqual(expect.arrayContaining(["scheduled", "ad_hoc"]));
|
||||
expect(adminCodes).toEqual(expect.arrayContaining(["activate", "resend"]));
|
||||
expect(failDetails).toEqual(expect.arrayContaining(["facebook.login_wall"]));
|
||||
expect(goneWhats).toEqual(expect.arrayContaining(["playlist", "download", "job"]));
|
||||
@@ -288,10 +301,23 @@ describe("backend ids have their names in both languages", () => {
|
||||
expect(adminCodes.filter((c) => !adminRefusals.some((m) => m[1] === c))).toEqual([]);
|
||||
});
|
||||
|
||||
it("the run list's status chips are the server's run statuses", () => {
|
||||
expect([...RUN_STATUSES].sort()).toEqual([...runStatuses].sort());
|
||||
});
|
||||
|
||||
for (const lang of ["en", "hu"]) {
|
||||
const keys = keysOf(lang);
|
||||
const missing = (wanted: string[]) => wanted.filter((k) => !keys.has(k));
|
||||
|
||||
it(`${lang}: every run status and trigger`, () => {
|
||||
expect(
|
||||
missing([
|
||||
...runStatuses.flatMap((s) => [`activity.runs.status.${s}`, `scheduler.dot.${s}`]),
|
||||
...runTriggers.map((s) => `scheduler.trigger.${s}`),
|
||||
]),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it(`${lang}: every config key and group`, () => {
|
||||
expect(
|
||||
missing([
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"24h": "24 hours",
|
||||
"7d": "7 days",
|
||||
"30d": "30 days",
|
||||
"90d": "90 days"
|
||||
"90d": "90 days",
|
||||
"all": "All"
|
||||
},
|
||||
"account": "Account",
|
||||
"any": "Any",
|
||||
@@ -26,7 +27,8 @@
|
||||
"older": "Load older",
|
||||
"stale": "Couldn't refresh — what you see may be out of date",
|
||||
"olderFailed": "Couldn't load the older rows",
|
||||
"countsFailed": "Couldn't load the filter counts",
|
||||
"filtersFailed": "Couldn't load the filter choices: the menus may be missing some.",
|
||||
"accountsFailed": "Couldn't load the accounts for the account filter.",
|
||||
"runs": {
|
||||
"job": "Job",
|
||||
"statusLabel": "Filter by status",
|
||||
@@ -57,10 +59,15 @@
|
||||
"action": "Action",
|
||||
"target": "Target",
|
||||
"requestLines": "Its request's log lines →",
|
||||
"noRequestLines": "Its request's log lines are no longer kept",
|
||||
"count_one": "{{count}} action shown · newest first",
|
||||
"count_other": "{{count}} actions shown · newest first"
|
||||
},
|
||||
"quota": {
|
||||
"total": "Spend over {{window}}: {{units}} units"
|
||||
"daily": "Spend per day",
|
||||
"total_one": "Spend over {{window}}: {{units}} unit",
|
||||
"total_other": "Spend over {{window}}: {{units}} units",
|
||||
"totalAll_one": "Spend over all kept history: {{units}} unit",
|
||||
"totalAll_other": "Spend over all kept history: {{units}} units"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"introAfter": " is shared background work (scheduled backfill, enrichment, resync) that isn't tied to one person.",
|
||||
"loading": "Loading…",
|
||||
"noUsageInRange": "No usage in this range.",
|
||||
"dailyTooltip": "{{day}}: {{units}} units",
|
||||
"system": "System (background)",
|
||||
"sync": {
|
||||
"myStatus": "My sync status",
|
||||
@@ -41,5 +40,7 @@
|
||||
"fullHistoryRequested_one": "Full history requested for {{count}} channel",
|
||||
"fullHistoryRequested_other": "Full history requested for {{count}} channels",
|
||||
"fullHistoryFailed": "Couldn't request full history"
|
||||
}
|
||||
},
|
||||
"dailyTooltip_one": "{{day}}: {{units}} unit",
|
||||
"dailyTooltip_other": "{{day}}: {{units}} units"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"24h": "24 óra",
|
||||
"7d": "7 nap",
|
||||
"30d": "30 nap",
|
||||
"90d": "90 nap"
|
||||
"90d": "90 nap",
|
||||
"all": "Mind"
|
||||
},
|
||||
"account": "Fiók",
|
||||
"any": "Bármelyik",
|
||||
@@ -26,7 +27,8 @@
|
||||
"older": "Régebbiek",
|
||||
"stale": "Nem sikerült frissíteni — amit látsz, elavult lehet",
|
||||
"olderFailed": "Nem sikerült betölteni a régebbi sorokat",
|
||||
"countsFailed": "Nem sikerült betölteni a szűrők darabszámait",
|
||||
"filtersFailed": "Nem sikerült betölteni a szűrők választékát: a menükből hiányozhatnak elemek.",
|
||||
"accountsFailed": "Nem sikerült betölteni a fiókokat a fiókszűrőhöz.",
|
||||
"runs": {
|
||||
"job": "Feladat",
|
||||
"statusLabel": "Szűrés állapotra",
|
||||
@@ -57,10 +59,15 @@
|
||||
"action": "Művelet",
|
||||
"target": "Cél",
|
||||
"requestLines": "A kérés naplósorai →",
|
||||
"noRequestLines": "A kérés naplósorai már nincsenek meg",
|
||||
"count_one": "{{count}} művelet látszik · a legújabb elöl",
|
||||
"count_other": "{{count}} művelet látszik · a legújabb elöl"
|
||||
},
|
||||
"quota": {
|
||||
"total": "Felhasználás ({{window}}): {{units}} egység"
|
||||
"daily": "Felhasználás naponta",
|
||||
"total_one": "Felhasználás ({{window}}): {{units}} egység",
|
||||
"total_other": "Felhasználás ({{window}}): {{units}} egység",
|
||||
"totalAll_one": "Felhasználás (a teljes megőrzött idő): {{units}} egység",
|
||||
"totalAll_other": "Felhasználás (a teljes megőrzött idő): {{units}} egység"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"introAfter": " a megosztott háttérmunka (ütemezett behúzás, dúsítás, újraszinkronizálás), amely nem köthető egy személyhez.",
|
||||
"loading": "Betöltés…",
|
||||
"noUsageInRange": "Nincs használat ebben az időszakban.",
|
||||
"dailyTooltip": "{{day}}: {{units}} egység",
|
||||
"system": "Rendszer (háttér)",
|
||||
"sync": {
|
||||
"myStatus": "Saját szinkronizálási állapot",
|
||||
@@ -41,5 +40,7 @@
|
||||
"fullHistoryRequested_one": "Teljes előzmény kérve {{count}} csatornához",
|
||||
"fullHistoryRequested_other": "Teljes előzmény kérve {{count}} csatornához",
|
||||
"fullHistoryFailed": "Nem sikerült teljes előzményt kérni"
|
||||
}
|
||||
},
|
||||
"dailyTooltip_one": "{{day}}: {{units}} egység",
|
||||
"dailyTooltip_other": "{{day}}: {{units}} egység"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user