Public: anyone can read this; the crew can change it.

GET …/map names its Server-Timing phases: session, graph, marks, pillars, ask-to-live, render #3995

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/3320-get-map-names-its-server-timing-phases-s → main 6 commits fork

  • Merged. scout/3320-get-map-names-its-server-timing-phases-s was merged into main as 61880ef.

Review

Sign in to review this change.
3 files changed +73 −18
src/http/api.ts +16 −17
@@ -3744,23 +3744,22 @@ export function apiRoutes(router: Router<Handler>): void {
37443744 router.add("GET", "/api/v1/repos/:owner/:name/map", async (ctx) => {
37453745 const repo = loadRepo(ctx);
37463746 if (!repo) return notFound();
3747 const graph = repoGraph(ctx.app.db, repo.id, {
3748 ...sealedScope(ctx.app.db, repo, ctx.auth.user),
3749 });
3750 const pillars = pillarView(ctx.app.db, repo);
3751 return json(
3752 mapJson(
3753 repo,
3754 graph,
3755 marksFor(
3756 ctx.app.db,
3757 repo.id,
3758 graph,
3759 listActiveRuns(ctx.app.db, [repo.id]),
3760 ),
3761 pillars,
3762 askToLiveFor(ctx.app, repo, pillars, ctx.auth.user),
3763 ),
3747 const { request } = ctx;
3748 notePhase(request, "session", takeSessionTime(request));
3749 const graph = timePhase(request, "graph", () =>
3750 repoGraph(ctx.app.db, repo.id, {
3751 ...sealedScope(ctx.app.db, repo, ctx.auth.user),
3752 }),
3753 );
3754 const marks = timePhase(request, "marks", () =>
3755 marksFor(ctx.app.db, repo.id, graph, listActiveRuns(ctx.app.db, [repo.id])),
3756 );
3757 const pillars = timePhase(request, "pillars", () => pillarView(ctx.app.db, repo));
3758 const ask = timePhase(request, "asktolive", () =>
3759 askToLiveFor(ctx.app, repo, pillars, ctx.auth.user),
3760 );
3761 return timePhase(request, "render", () =>
3762 json(mapJson(repo, graph, marks, pillars, ask)),
37643763 );
37653764 });
37663765 // What people asked of the crew in the repositories the caller can write —
test/map-api-timing.test.ts added +57
@@ -0,0 +1,57 @@
1 /** GET …/map names its Server-Timing phases: session, graph, marks, pillars, asktolive, render (#3320). */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtemp, rm } from "node:fs/promises";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createRepo } from "../src/actions/repos.ts";
8 import { addDependency } from "../src/core/dependencies.ts";
9 import { createIssue } from "../src/core/issues.ts";
10 import { serve, type Servers } from "../src/http/server.ts";
11 import { seedAdmin } from "./helpers/admin.ts";
12
13 let dataDir = "";
14 let app: App, servers: Servers, bearer = "";
15
16 beforeAll(async () => {
17 dataDir = await mkdtemp(join(tmpdir(), "forge-maptime-"));
18 app = createApp({ dataDir, port: 0 });
19 servers = await serve(app);
20 const admin = await seedAdmin(app, { username: "alice", email: "[email protected]", password: "correct-horse-battery" });
21 if (!admin.ok) throw new Error(admin.error);
22 const made = await createRepo(app, admin.value, { name: "notes", visibility: "public", description: "" });
23 if (!made.ok) throw new Error(String(made.error));
24 const issues = [];
25 for (let i = 0; i < 200; i++) {
26 const r = createIssue(app.db, { repoId: made.value.id, authorId: admin.value.id, title: `Issue ${i}` });
27 if (!r.ok) throw new Error(r.error);
28 issues.push(r.value);
29 }
30 for (let i = 0; i < 50; i++) {
31 const r = addDependency(app.db, issues[i + 1]!, issues[i]!, admin.value.id);
32 if (!r.ok) throw new Error(r.error);
33 }
34 const minted = await fetch(`${servers.url}/api/v1/tokens`, {
35 method: "POST",
36 headers: { "content-type": "application/json" },
37 body: JSON.stringify({ adminConfirm: true, username: "alice", password: "correct-horse-battery", name: "cli", scope: "admin" }),
38 });
39 bearer = ((await minted.json()) as { token: string }).token;
40 });
41
42 afterAll(async () => {
43 servers.stop();
44 app.close();
45 await rm(dataDir, { recursive: true, force: true });
46 });
47
48 test("the repository map names its phases in order, with durations", async () => {
49 const res = await fetch(`${servers.url}/api/v1/repos/alice/notes/map`, { headers: { authorization: `Bearer ${bearer}` } });
50 expect(res.status).toBe(200);
51 const body = (await res.json()) as { ready: unknown[] };
52 expect(body.ready.length).toBeGreaterThan(0);
53 const parts = (res.headers.get("server-timing") ?? "").split(", ");
54 const names = parts.map((p) => p.split(";")[0]!);
55 expect(names.filter((n) => n !== "app")).toEqual(["session", "graph", "marks", "pillars", "asktolive", "render"]);
56 for (const p of parts) expect(Number(/dur=([\d.]+)/.exec(p)?.[1])).not.toBeNaN();
57 });
test/route-timing-coverage.test.ts −1
@@ -19,7 +19,6 @@ import { seedAdmin } from "./helpers/admin.ts";
1919
2020 /** Routes that answer 200 without a `session` phase → why. */
2121 const UNTIMED: Record<string, string> = {
22 "/api/v1/repos/:owner/:name/map": "untimed read today: answers without a session phase",
2322 "/metrics": "Prometheus text exposition, scraped by token; not a page read",
2423 };
2524

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs