perf(web): restore last known dashboard tiles

This commit is contained in:
vince 2026-06-20 14:25:43 +02:00
parent 243d71686d
commit 4ce42fb52b
4 changed files with 412 additions and 8 deletions

View file

@ -9,10 +9,13 @@ import type {
import type { DashboardRuntimeState } from "$lib/server/dashboard";
import {
attachDashboardRefreshLifecycle,
createDashboardTileSnapshotStore,
createDashboardTileBackoff,
createDashboardRequestAborter,
runDashboardHydrationQueue,
shouldPauseDashboardRefresh,
type DashboardTileSnapshotStoreContext,
type RestoredDashboardTileSnapshot,
} from "$lib/client/dashboard-refresh";
import { dashboardDocumentToUiDashboard } from "$lib/ui-adapter/model-renderer";
import {
@ -47,6 +50,7 @@ type DashboardTileResponse =
type DashboardTileHydrationResult = "aborted" | "failed" | "ready";
const dashboardTileHydrationConcurrency = 6;
type DashboardTileSnapshotStore = ReturnType<typeof createDashboardTileSnapshotStore>;
export function AppStateView({
dashboard,
@ -164,6 +168,9 @@ export default function App() {
let hydrationRun = 0;
const requestAborter = createDashboardRequestAborter();
const tileBackoff = createDashboardTileBackoff();
const tileSnapshotStore = createDashboardTileSnapshotStore(
getTileSnapshotStorage(),
);
function refreshPaused() {
return shouldPauseDashboardRefresh({
@ -195,19 +202,42 @@ export default function App() {
const nextDashboard = (await response.json()) as DashboardRuntimeState;
if (cancelled || shellSignal.aborted || refreshPaused()) return;
setDashboard(nextDashboard);
const restored = restoreDashboardTileSnapshots(
nextDashboard,
nextDashboard.state === "ready"
? tileSnapshotStore.restore({
currentRevisionId: nextDashboard.currentRevisionId,
schemaVersion: nextDashboard.schemaVersion,
})
: [],
);
setDashboard(restored.dashboard);
const currentRun = ++hydrationRun;
if (
nextDashboard.state === "ready" &&
nextDashboard.liveDatasourceHydration?.enabled !== false
restored.dashboard.state === "ready" &&
restored.dashboard.liveDatasourceHydration?.enabled !== false
) {
const tiles = dashboardHydrationTiles(nextDashboard.document).filter(
const tiles = dashboardHydrationTiles(restored.dashboard.document).filter(
(tile) => tileBackoff.canAttempt(dashboardTileKey(tile)),
);
const tileSignal = requestAborter.beginTileRun();
setHydratingItemIds(new Set(tiles.map(dashboardTileKey)));
hydrateDashboardTiles(tiles, currentRun, tileSignal);
setHydratingItemIds(
new Set(
tiles
.map(dashboardTileKey)
.filter((key) => !restored.restoredItemIds.has(key)),
),
);
hydrateDashboardTiles(
tiles,
currentRun,
tileSignal,
{
currentRevisionId: restored.dashboard.currentRevisionId,
schemaVersion: restored.dashboard.schemaVersion,
},
);
} else {
setHydratingItemIds(new Set());
}
@ -236,12 +266,18 @@ export default function App() {
tiles: DashboardTileReference[],
run: number,
signal: AbortSignal,
snapshotContext: DashboardTileSnapshotStoreContext,
) {
void runDashboardHydrationQueue({
concurrency: dashboardTileHydrationConcurrency,
hydrate: async (tile) => {
const key = dashboardTileKey(tile);
const result = await hydrateDashboardTile(tile, run, signal);
const result = await hydrateDashboardTile(
tile,
run,
signal,
snapshotContext,
);
if (result === "ready") {
tileBackoff.recordSuccess(key);
@ -258,6 +294,7 @@ export default function App() {
tile: DashboardTileReference,
run: number,
signal: AbortSignal,
snapshotContext: DashboardTileSnapshotStoreContext,
): Promise<DashboardTileHydrationResult> {
const key = dashboardTileKey(tile);
@ -286,6 +323,10 @@ export default function App() {
}
: current,
);
tileSnapshotStore.saveReadyTile({
...snapshotContext,
response: readyTileResponse,
});
return "ready";
} catch (error) {
if (!isAbortError(error) && !cancelled) {
@ -352,6 +393,14 @@ function getThemeStorage(): Storage | undefined {
}
}
function getTileSnapshotStorage(): Storage | undefined {
try {
return window.localStorage;
} catch {
return undefined;
}
}
function isAbortError(error: unknown): boolean {
return (
typeof error === "object" &&
@ -361,6 +410,41 @@ function isAbortError(error: unknown): boolean {
);
}
export function restoreDashboardTileSnapshots(
dashboard: DashboardRuntimeState,
snapshots: RestoredDashboardTileSnapshot[],
): {
dashboard: DashboardRuntimeState;
restoredItemIds: Set<string>;
} {
if (dashboard.state !== "ready" || snapshots.length === 0) {
return {
dashboard,
restoredItemIds: new Set(),
};
}
return snapshots.reduce(
(current, snapshot) => ({
dashboard: {
...current.dashboard,
document: applyDashboardTile(
current.dashboard.document,
snapshot.response as Extract<DashboardTileResponse, { state: "ready" }>,
),
},
restoredItemIds: new Set([
...current.restoredItemIds,
dashboardTileKey(snapshot.response.tile),
]),
}),
{
dashboard,
restoredItemIds: new Set<string>(),
},
);
}
function markHydratingItems(
dashboard: UiDashboardPreview,
hydratingItemIds?: ReadonlySet<string>,