Improve metrics clarity, exploration, and refresh reliability

This commit is contained in:
2026-09-27 22:12:11 -04:00
parent 981fb27c1c
commit 3922e28ca8
15 changed files with 984 additions and 135 deletions
+64
View File
@@ -0,0 +1,64 @@
import type { MetricsData } from '$lib/metrics.js';
interface Options {
getQuery: () => string;
onData: (report: MetricsData) => void;
onError: (message: string | null) => void;
onLoading: (loading: boolean) => void;
fetcher?: typeof fetch;
timeoutMs?: number;
}
/** Keeps background failures and obsolete responses out of the last successful report. */
export function createMetricsRefresh(options: Options) {
const request = options.fetcher ?? fetch;
let generation = 0;
let pending: { query: string; controller: AbortController; promise: Promise<void>; timer?: ReturnType<typeof setTimeout> } | null = null;
function cancel() {
generation++;
if (pending) {
clearTimeout(pending.timer);
pending.controller.abort();
pending = null;
}
options.onLoading(false);
}
function refresh(): Promise<void> {
const query = options.getQuery();
if (pending?.query === query) return pending.promise;
cancel();
const id = generation;
const controller = new AbortController();
let timedOut = false;
const current = () => generation === id && options.getQuery() === query;
options.onLoading(true);
options.onError(null);
const entry = { query, controller, promise: Promise.resolve(), timer: undefined as ReturnType<typeof setTimeout> | undefined };
pending = entry;
entry.timer = setTimeout(() => { timedOut = true; controller.abort(); }, options.timeoutMs ?? 15_000);
entry.promise = (async () => {
try {
const response = await request(`/api/metrics?${query}`, { signal: controller.signal, cache: 'no-store' });
if (!response.ok) throw new Error(`Report refresh failed (${response.status}).`);
const report = await response.json() as MetricsData;
if (!report || !Array.isArray(report.points) || !Array.isArray(report.maps) || !Array.isArray(report.servers) || !report.summary) {
throw new Error('The metrics response was incomplete.');
}
if (current()) options.onData(report);
} catch (error) {
if (current() && (!controller.signal.aborted || timedOut)) {
options.onError(timedOut ? 'Report refresh timed out. Try again.' : error instanceof Error ? error.message : 'Unable to refresh metrics. Try again.');
}
} finally {
clearTimeout(entry.timer);
if (pending === entry) pending = null;
if (current()) options.onLoading(false);
}
})();
return entry.promise;
}
return { refresh, cancel };
}