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

A repository's pulls list's Server-Timing names its phases: session, each read, render #4019

Merged opened this

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

Merged

scout/forge:scout/3323-a-repository-s-pulls-list-s-server-timin → main 2 commits fork 1 thread · 1 unresolved

  • Merged. scout/3323-a-repository-s-pulls-list-s-server-timin was merged into main as af0c001.

Review

Sign in to review this change.
2 files changed +62 −1
src/web/studio/lists.ts +16 −1
@@ -472,23 +472,36 @@ export async function tellsList(ctx: Ctx): Promise<Response> {
472472 * step deeper, on the change. Narrowed as `tellsList` is.
473473 */
474474 export async function changesList(ctx: Ctx): Promise<Response> {
475 const started = performance.now();
475476 const repo = loadRepo(ctx);
476477 if (!repo) return notFoundPage(ctx);
477478 const db = ctx.app.db;
478479 const base = repoBase(repo);
479480 const f = readFacets(ctx);
480481 const { options, record, wordCtx } = listRead(ctx, repo, f);
482 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
481483
484 let mark = performance.now();
485 notePhase(ctx.request, "repo", mark - started);
486 const lap = (name: string): void => {
487 const now = performance.now();

This phase measures from function start through listRead/readFacets, not just the repo load. Take a mark right after loadRepo, or rename the phase.

488 notePhase(ctx.request, name, now - mark);
489 mark = now;
490 };
482491 const fetched = listPulls(db, repo.id, {
483492 ...options,
484493 ...sealedScope(db, repo, ctx.auth.user),
485494 });
495 lap("pulls");
486496 const words = await Promise.all(
487497 fetched.map((pull) => pullWord(wordCtx, pull)),
488498 );
499 lap("words");
489500 const answered = tellsAnswered(db, repo.id, fetched);
501 lap("tells");
490502 const labels = labelsForIssues(db, fetched.map((pull) => pull.id));
491503 const repoLabels = listLabels(db, repo.id);
504 lap("labels");
492505 const rows = fetched
493506 .map((pull, i) => ({ pull, info: words[i]! }))
494507 .filter((r) => shown(r.info.word, f));
@@ -544,7 +557,7 @@ export async function changesList(ctx: Ctx): Promise<Response> {
544557 </div>
545558 ${liveMount({ repo: base })}`;
546559
547 return page({
560 const res = page({
548561 ctx,
549562 title: pageTitle("changes", repo, record !== undefined),
550563 crumbs: crumbsFor(repo),
@@ -553,6 +566,8 @@ export async function changesList(ctx: Ctx): Promise<Response> {
553566 styleCss: labelCss([...repoLabels, ...[...labels.values()].flat()]),
554567 scriptJs: liveClientJs,
555568 });
569 lap("render");
570 return res;
556571 }
557572
558573 /** Which runs a list shows: all three kinds by default, or the one that
test/pulls-list-timing.test.ts added +46
@@ -0,0 +1,46 @@
1 /** A repository's pulls list names its phases in Server-Timing (#3323). */
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-pltime-"));
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 function names(res: Response): string[] {
37 return (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!);
38 }
39
40 test("a repository's pulls list names session, repo, pulls, words, tells, labels and render in order", async () => {
41 const res = await fetch(`${servers.url}/alice/notes/pulls`, { headers: { authorization: `Bearer ${bearer}` } });
42 expect(res.status).toBe(200);
43 await res.text();
44 expect(res.headers.get("x-content-type-options")).toBe("nosniff");
45 expect(names(res)).toEqual(["app", "session", "repo", "pulls", "words", "tells", "labels", "render"]);
46 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs