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

A repository's waves page's Server-Timing names its phases: session, each read, render #4027

Merged opened this

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

Merged

scout/forge:scout/3282-a-repository-s-waves-page-s-server-timin → main 5 commits fork 1 thread · 1 unresolved

  • Merged. scout/3282-a-repository-s-waves-page-s-server-timin was merged into main as 7e03296.

Review

Sign in to review this change.
2 files changed +92 −5
src/web/pages/waves.ts +8 −5
@@ -19,14 +19,15 @@ import { localScope } from "../scope.ts";
1919
2020 // GET /:owner/:name/waves
2121 export async function wavesPage(ctx: Ctx): Promise<Response> {
22 const repo = loadRepo(ctx);
22 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
23 const repo = timePhase(ctx.request, "repo", () => loadRepo(ctx));
2324 if (!repo) return notFoundPage(ctx);
2425 const scope = localScope(ctx, repo);
2526 const branch = repo.defaultBranch;
26 const entries = await readWaves(
27 repoDir(ctx.app.reposRoot, repo.ownerName, repo.name),
28 branch,
27 const entries = await timePhaseAsync(ctx.request, "waves", () =>
28 readWaves(repoDir(ctx.app.reposRoot, repo.ownerName, repo.name), branch),
2929 );
30 const rendering = performance.now();
3031 const fileHref = (file: string): string =>
3132 VERSION.test(file.slice(0, -3))
3233 ? `${repoBase(repo)}/waves/${encodeURIComponent(file.slice(0, -3))}`
@@ -53,13 +54,15 @@ export async function wavesPage(ctx: Ctx): Promise<Response> {
5354 : listCard(rows, { head: html`<h2>Waves</h2>` })}
5455 </div>`;
5556
56 return page({
57 const response = page({
5758 ctx,
5859 title: `Waves · ${scope.facts.name}`,
5960 crumbs: scope.crumbs,
6061 palette: scope.palette,
6162 body,
6263 });
64 notePhase(ctx.request, "render", performance.now() - rendering);
65 return response;
6366 }
6467
6568 /** Only http(s) becomes a link; anything else is shown as plain text. */
test/waves-timing.test.ts added +84
@@ -0,0 +1,84 @@
1 /** `/:owner/:name/waves` names session, repo, waves and render in Server-Timing (#3282). */

The header comment cites #3282; this pull is #4027. Fix the reference.

2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtempSync, rmSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { commitToBranch, createRepo } from "../src/actions/repos.ts";
7 import { createApp, type App } from "../src/app.ts";
8 import { createUser } from "../src/core/users.ts";
9 import { serve, type Servers } from "../src/http/server.ts";
10 import { proveAll } from "./helpers/verified.ts";
11 import { writeWave } from "../src/story/wave.ts";
12 import { quietLogs } from "./helpers/globals.ts";
13
14 quietLogs();
15
16 let root = "";
17 let app: App;
18 let servers: Servers;
19
20 beforeAll(async () => {
21 root = mkdtempSync(join(tmpdir(), "forge-waves-timing-"));
22 app = createApp({ dataDir: root, port: 0, forgeRepo: "alice/forge" });
23 const u = await createUser(app.db, {
24 origin: "seed",
25 trusted: true,
26 username: "alice",
27 email: "[email protected]",
28 password: "a-good-passphrase",
29 adminIfFirst: true,
30 });
31 if (!u.ok) throw new Error(u.error);
32 proveAll(app);
33 const made = await createRepo(app, { id: u.value.id, username: "alice" } as never, { name: "forge" });
34 if (!made.ok) throw new Error(made.error);
35 const w = writeWave({
36 version: "0.1.0",
37 date: "2026-01-01",
38 headline: "First",
39 highlights: [1, 2, 3].map((n) => ({ title: `Thing ${n}`, why: "because" })),
40 pulls: [{ title: "#1 A pull", url: "https://forge.example/o/r/pulls/1" }],
41 });
42 if (!w.ok) throw new Error("wave");
43 const put = await commitToBranch(app, { id: u.value.id, username: "alice" } as never, made.value, {
44 message: "wave",
45 files: [{ path: "waves/0.1.0.md", content: w.value }],
46 });
47 if (!put.ok) throw new Error(JSON.stringify(put.error));
48 servers = await serve(app);
49 });
50 afterAll(() => {
51 servers.stop();
52 app.close();
53 rmSync(root, { recursive: true, force: true });
54 });
55
56 const phasesOf = (read: string): string[] => ["app", "session", "repo", read, "render"];
57 const signin = async (): Promise<string> => {
58 const res = await fetch(`${servers.url}/signin`, {
59 method: "POST",
60 headers: { origin: servers.url },
61 body: new URLSearchParams({ username: "alice", password: "a-good-passphrase" }),
62 redirect: "manual",
63 });
64 return (res.headers.get("set-cookie") ?? "").split(";")[0]!;
65 };
66 const expectPhases = (res: Response, read: string): void => {
67 const parts = (res.headers.get("server-timing") ?? "").split(", ");
68 for (const name of phasesOf(read))
69 expect(parts.find((p) => p.startsWith(`${name};`))).toMatch(/^[a-z]+;dur=\d+\.\d$/);
70 };
71
72 test("GET /:owner/:name/waves names its phases, listing waves", async () => {
73 const res = await fetch(`${servers.url}/alice/forge/waves`, { headers: { cookie: await signin() } });
74 expect(res.status).toBe(200);
75 expect(await res.text()).toContain("First");
76 expectPhases(res, "waves");
77 });
78
79 test("GET /:owner/:name/waves/:version names its phases", async () => {
80 const res = await fetch(`${servers.url}/alice/forge/waves/0.1.0`, { headers: { cookie: await signin() } });
81 expect(res.status).toBe(200);
82 expect(await res.text()).toContain("Thing 1");
83 expectPhases(res, "wave");
84 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs