perf(web): batch dashboard tile hydration
This commit is contained in:
parent
63b008095f
commit
de87464ef1
7 changed files with 518 additions and 40 deletions
|
|
@ -3,8 +3,10 @@ import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
|
|||
import {
|
||||
createDashboardTileCache,
|
||||
dashboardTileCacheKey,
|
||||
handleDashboardTilesRoute,
|
||||
handleDashboardTileRoute,
|
||||
loadDashboardResponse,
|
||||
loadDashboardTilesResponse,
|
||||
loadDashboardTileResponse,
|
||||
type DashboardTileResolutionLogEvent,
|
||||
} from "./dashboard";
|
||||
|
|
@ -563,6 +565,115 @@ describe("dashboard API route", () => {
|
|||
);
|
||||
});
|
||||
|
||||
test("serves batch tile route responses", async () => {
|
||||
const response = await handleDashboardTilesRoute(
|
||||
new Request("https://example.test/api/dashboard/tiles", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
tiles: [
|
||||
{
|
||||
kind: "status",
|
||||
stripId: "footer-status",
|
||||
id: "auto-refresh",
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
{
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: createDashboardTileCache(),
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("cache-control")).toBe(
|
||||
"private, max-age=5, stale-while-revalidate=30",
|
||||
);
|
||||
await expect(response.json()).resolves.toMatchObject({
|
||||
state: "ready",
|
||||
tiles: [
|
||||
{
|
||||
state: "ready",
|
||||
tile: {
|
||||
kind: "status",
|
||||
stripId: "footer-status",
|
||||
id: "auto-refresh",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("rejects invalid batch tile requests", async () => {
|
||||
const response = await handleDashboardTilesRoute(
|
||||
new Request("https://example.test/api/dashboard/tiles", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
tiles: [{ kind: "service", id: "missing-group" }],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
|
||||
test("resolves batch tile responses with server concurrency capped at six", async () => {
|
||||
let active = 0;
|
||||
let maxActive = 0;
|
||||
const started: string[] = [];
|
||||
const releases = new Map<string, () => void>();
|
||||
const service = dimensionLabDashboardFixture.serviceGroups
|
||||
.flatMap((group) => group.services)[0];
|
||||
if (!service) throw new Error("missing service fixture");
|
||||
const tiles = Array.from({ length: 7 }, (_, index) => ({
|
||||
kind: "service" as const,
|
||||
groupId: "essentials",
|
||||
id: `service-${index}`,
|
||||
}));
|
||||
|
||||
const batch = loadDashboardTilesResponse(tiles, {
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: {
|
||||
async resolve(key) {
|
||||
active += 1;
|
||||
maxActive = Math.max(maxActive, active);
|
||||
started.push(key);
|
||||
await new Promise<void>((resolve) => releases.set(key, resolve));
|
||||
active -= 1;
|
||||
|
||||
return {
|
||||
cache: "miss",
|
||||
coalesced: false,
|
||||
response: {
|
||||
state: "ready",
|
||||
tile: JSON.parse(key),
|
||||
item: service,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => started.length === 6);
|
||||
expect(maxActive).toBe(6);
|
||||
releases.get(started[0])?.();
|
||||
await waitFor(() => started.length === 7);
|
||||
for (const release of releases.values()) release();
|
||||
|
||||
await expect(batch).resolves.toMatchObject({
|
||||
state: "ready",
|
||||
tiles: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
state: "ready",
|
||||
tile: tiles[0],
|
||||
}),
|
||||
]),
|
||||
});
|
||||
expect(maxActive).toBe(6);
|
||||
});
|
||||
|
||||
test("does not hydrate tile routes when live datasources are disabled", async () => {
|
||||
const previous = process.env.DISABLE_LIVE_DATASOURCES;
|
||||
process.env.DISABLE_LIVE_DATASOURCES = "1";
|
||||
|
|
@ -598,6 +709,15 @@ function jsonResponse(payload: unknown): Response {
|
|||
});
|
||||
}
|
||||
|
||||
async function waitFor(predicate: () => boolean) {
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
if (predicate()) return;
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
throw new Error("condition was not met");
|
||||
}
|
||||
|
||||
function telemetryFetch() {
|
||||
return vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue