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

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

Merged opened this

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

Merged

scout/forge:scout/3310-a-repository-s-forks-page-s-server-timin → main 3 commits fork 1 thread · 1 unresolved

  • Merged. scout/3310-a-repository-s-forks-page-s-server-timin was merged into main as 6a1fee2.

Review

Sign in to review this change.
2 files changed +66 −1
src/web/pages/repo.ts +15 −1
@@ -131,16 +131,24 @@ function updatable(drift: ForkDrift | undefined): boolean {
131131 * see, newest first. The count on the repository page is the way in.
132132 */
133133 export async function forksPage(ctx: Ctx): Promise<Response> {
134 notePhase(ctx.request, "session", takeSessionTime(ctx.request));

The repeated t = performance.now() / notePhase pairs work, but a small timed(ctx, name, fn) helper in route_timing would stop each page from re-implementing this. Not blocking.

135 let t = performance.now();
134136 const repo = loadRepo(ctx);
137 notePhase(ctx.request, "repository", performance.now() - t);
135138 if (!repo) return notFoundPage(ctx);
139 t = performance.now();
136140 const forks = listForks(ctx.app.db, repo.id).filter((fork) =>
137141 canRead(ctx.app.db, fork, ctx.auth.user),
138142 );
143 notePhase(ctx.request, "forks", performance.now() - t);
144 t = performance.now();
139145 // The whole family, from the oldest ancestor this viewer may see: who
140146 // follows whom, and where each stands against the one it follows.
141147 const lineage = await lineageOf(ctx.app, repo, (r) =>
142148 canRead(ctx.app.db, r, ctx.auth.user),
143149 );
150 notePhase(ctx.request, "lineage", performance.now() - t);
151 t = performance.now();
144152 const body = html`${repoHeader(ctx, repo, "forks")}
145153 <div class="page-head mt">
146154 <h2>${forks.length} fork${forks.length === 1 ? "" : "s"}</h2>
@@ -152,7 +160,13 @@ export async function forksPage(ctx: Ctx): Promise<Response> {
152160 note: "A fork is a copy under another name that can propose changes back here, and follows this one until it has work of its own.",
153161 })
154162 : lineageCard(lineage, repo.id)}`;
155 return page({ ctx, title: `Forks · ${repo.ownerName}/${repo.name}`, body });
163 const res = page({
164 ctx,
165 title: `Forks · ${repo.ownerName}/${repo.name}`,
166 body,
167 });
168 notePhase(ctx.request, "render", performance.now() - t);
169 return res;
156170 }
157171
158172 /** The "forked from …" lineage line and a Fork button, for the repo home. */
test/forks-timing.test.ts added +51
@@ -0,0 +1,51 @@
1 /** A repository's forks page names its phases in Server-Timing (#3310, #3334). */
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 { serve, type Servers } from "../src/http/server.ts";
9 import { seedAdmin } from "./helpers/admin.ts";
10
11 let dataDir = "";
12 let app: App, servers: Servers, bearer = "";
13
14 beforeAll(async () => {
15 dataDir = await mkdtemp(join(tmpdir(), "forge-fktime-"));
16 app = createApp({ dataDir, port: 0 });
17 servers = await serve(app);
18 const admin = await seedAdmin(app, { username: "alice", email: "[email protected]", password: "correct-horse-battery" });
19 if (!admin.ok) throw new Error(admin.error);
20 const made = await createRepo(app, admin.value, { name: "notes", visibility: "public", description: "" });
21 if (!made.ok) throw new Error(String(made.error));
22 const minted = await fetch(`${servers.url}/api/v1/tokens`, {
23 method: "POST",
24 headers: { "content-type": "application/json" },
25 body: JSON.stringify({ adminConfirm: true, username: "alice", password: "correct-horse-battery", name: "cli", scope: "admin" }),
26 });
27 bearer = ((await minted.json()) as { token: string }).token;
28 });
29
30 afterAll(async () => {
31 servers.stop();
32 app.close();
33 await rm(dataDir, { recursive: true, force: true });
34 });
35
36 const names = (res: Response): string[] =>
37 (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!).filter((n) => n !== "app");
38
39 test("the forks page names session, repository, forks, lineage and render, in order", async () => {
40 const res = await fetch(`${servers.url}/alice/notes/forks`, { headers: { authorization: `Bearer ${bearer}` } });
41 expect(res.status).toBe(200);
42 await res.text();
43 expect(names(res)).toEqual(["session", "repository", "forks", "lineage", "render"]);
44 });
45
46 test("a missing repository still names session and repository", async () => {
47 const res = await fetch(`${servers.url}/alice/nope/forks`, { headers: { authorization: `Bearer ${bearer}` } });
48 expect(res.status).toBe(404);
49 await res.text();
50 expect(names(res)).toEqual(["session", "repository"]);
51 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs