Improve metrics clarity, exploration, and refresh reliability
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user