fix(diagnostics): count the 24 hours from the moment the level was raised
The countdown's clock was the moment Settings mounted, so a level raised two minutes after opening the page read "24 h 2 m" until the first tick (measured on prod). The clock is read at render; the interval only re-renders.
This commit is contained in:
@@ -16,8 +16,14 @@ vi.mock("react-i18next", async (importOriginal) => {
|
|||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
useTranslation: () => ({
|
useTranslation: () => ({
|
||||||
t: (key: string, opts?: { count?: number }) =>
|
t: (key: string, opts?: { count?: number; h?: number; m?: number; time?: string }) =>
|
||||||
opts?.count != null ? `${key}:${opts.count}` : key,
|
opts?.count != null
|
||||||
|
? `${key}:${opts.count}`
|
||||||
|
: opts?.m != null
|
||||||
|
? `${key}:${opts.h ?? 0}h${opts.m}m`
|
||||||
|
: opts?.time != null
|
||||||
|
? `${key}:${opts.time}`
|
||||||
|
: key,
|
||||||
i18n: { language: "en" },
|
i18n: { language: "en" },
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -106,6 +112,23 @@ describe("DiagnosticsSettings", () => {
|
|||||||
expect(setLevel).toHaveBeenLastCalledWith("INFO");
|
expect(setLevel).toHaveBeenLastCalledWith("INFO");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("counts the 24 hours from the moment the level was raised, not from when the page opened", async () => {
|
||||||
|
vi.useFakeTimers({ toFake: ["Date"] });
|
||||||
|
try {
|
||||||
|
vi.setSystemTime(new Date("2026-09-19T02:54:00Z"));
|
||||||
|
mount({});
|
||||||
|
vi.setSystemTime(new Date("2026-09-19T02:56:30Z")); // the page sat open for 2.5 minutes
|
||||||
|
fireEvent.click(levelButton("settings.diagnostics.levels.DEBUG"));
|
||||||
|
fireEvent.click(screen.getByTestId("diagnostics-consent-confirm"));
|
||||||
|
await waitFor(() => expect(screen.getByTestId("diagnostics-status")).toBeTruthy());
|
||||||
|
expect(screen.getByTestId("diagnostics-status").textContent).toContain(
|
||||||
|
"settings.diagnostics.hoursMinutes:24h0m",
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
vi.useRealTimers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("going back to the base level needs no consent", async () => {
|
it("going back to the base level needs no consent", async () => {
|
||||||
const until = new Date(Date.now() + 3_600_000).toISOString();
|
const until = new Date(Date.now() + 3_600_000).toISOString();
|
||||||
mount({ diagnostics: { level: "DEBUG", until, retention_days: 7 } });
|
mount({ diagnostics: { level: "DEBUG", until, retention_days: 7 } });
|
||||||
|
|||||||
@@ -15,14 +15,17 @@ const LEVELS: readonly DiagnosticsLevel[] = ["INFO", "DEBUG", "TRACE"];
|
|||||||
|
|
||||||
/** Hours and minutes until `until`, refreshed every 30 s — the status line counts down. */
|
/** Hours and minutes until `until`, refreshed every 30 s — the status line counts down. */
|
||||||
function useRemaining(until: string | null): { h: number; m: number } | null {
|
function useRemaining(until: string | null): { h: number; m: number } | null {
|
||||||
const [now, setNow] = useState(() => Date.now());
|
// The clock is read at render, and the interval only re-renders: a `now` held in state was the
|
||||||
|
// moment the page MOUNTED, so a level raised two minutes after opening Settings read "24 h 2 m"
|
||||||
|
// until the first tick (prod UAT 2026-09-19).
|
||||||
|
const [, tick] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!until) return;
|
if (!until) return;
|
||||||
const id = window.setInterval(() => setNow(Date.now()), 30_000);
|
const id = window.setInterval(() => tick((n) => n + 1), 30_000);
|
||||||
return () => window.clearInterval(id);
|
return () => window.clearInterval(id);
|
||||||
}, [until]);
|
}, [until]);
|
||||||
if (!until) return null;
|
if (!until) return null;
|
||||||
const minutes = Math.max(0, Math.ceil((Date.parse(until) - now) / 60_000));
|
const minutes = Math.max(0, Math.ceil((Date.parse(until) - Date.now()) / 60_000));
|
||||||
return { h: Math.floor(minutes / 60), m: minutes % 60 };
|
return { h: Math.floor(minutes / 60), m: minutes % 60 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user