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

/releases names its Server-Timing phases and checks every row's rollback at once, not one by one #3262

Merged opened this

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

Merged

scout/forge:scout/3260-releases-names-its-server-timing-phases → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/3260-releases-names-its-server-timing-phases was merged into main as 70fcada.

Review

Sign in to review this change.
2 files changed +90 −3
src/web/pages/releases.ts +15 −3
@@ -8,6 +8,7 @@
88 */
99
1010 import { getRepoById } from "../../core/repos.ts";
11 import { notePhase, takeSessionTime } from "../../core/route_timing.ts";
1112 import type { User } from "../../core/users.ts";
1213 import { running } from "../../kernel/version.ts";
1314 import type { Ctx } from "../../http/context.ts";
@@ -37,16 +38,25 @@ function pullCell(ctx: Ctx, r: Release): Html {
3738 }
3839
3940 export async function releasesPage(ctx: Ctx): Promise<Response> {
41 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
4042 const user = ctx.auth.user;
4143 if (user === undefined) return signInFirst(ctx);
4244 if (!mayRead(ctx.app, user)) return notFoundPage(ctx);
45 const reading = performance.now();
4346 const releases = listReleases(ctx.app.db);
47 notePhase(ctx.request, "releases", performance.now() - reading);
4448 const live = running().commit;
4549 const writer = mayRollBack(ctx.app, user);
50 const checking = performance.now();
51 const rollable = writer
52 ? await Promise.all(releases.map((r) => (live !== null && r.sha === live ? false : canRollBackTo(ctx.app, r))))
53 : releases.map(() => false);
54 notePhase(ctx.request, "rollback", performance.now() - checking);
55 const rendering = performance.now();
4656 const rows: Html[] = [];
47 for (const r of releases) {
57 for (const [i, r] of releases.entries()) {
4858 const isLive = live !== null && r.sha === live;
49 const button = writer && !isLive && (await canRollBackTo(ctx.app, r));
59 const button = rollable[i] === true;
5060 rows.push(html`<tr>
5161 <td>${sha(r.sha)} ${when(isLive, () => badge("live", { tone: "success" }))}
5262 ${when(!r.ok, () => badge("preflight failed", { tone: "danger" }))}</td>
@@ -65,11 +75,13 @@ export async function releasesPage(ctx: Ctx): Promise<Response> {
6575 <thead><tr><th>Release</th><th>Pull</th><th>Landed</th><th>Healthy</th><th></th></tr></thead>
6676 <tbody>${rows}</tbody>
6777 </table>`);
68 return page({
78 const response = page({
6979 ctx,
7080 title: "Releases",
7181 body: html`<div class="page-head"><h1>Releases</h1></div>${body}`,
7282 });
83 notePhase(ctx.request, "render", performance.now() - rendering);
84 return response;
7385 }
7486
7587 export async function rollbackSubmit(ctx: Ctx): Promise<Response> {
test/releases-timing.test.ts added +75
@@ -0,0 +1,75 @@
1 /** `/releases` names session, releases, rollback and render in Server-Timing (#3260). */

The header cites #3260, but this pull is #3262. Fix the reference.

2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { 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 { releaseDir } from "../src/release/checkout.ts";
11 import { recordRelease } from "../src/release/record.ts";
12 import { proveAll } from "./helpers/verified.ts";
13 import { quietLogs } from "./helpers/globals.ts";
14
15 quietLogs();
16
17 const LIVE = "a".repeat(40);
18 const KEPT = ["b".repeat(40), "c".repeat(40)];
19
20 let root = "";
21 let app: App;
22 let servers: Servers;
23 const was = process.env["FORGE_COMMIT"];
24
25 beforeAll(async () => {
26 process.env["FORGE_COMMIT"] = LIVE;
27 root = mkdtempSync(join(tmpdir(), "forge-releases-timing-"));
28 app = createApp({ dataDir: root, port: 0, forgeRepo: "alice/forge" });
29 const u = await createUser(app.db, {
30 origin: "seed",
31 trusted: true,
32 username: "alice",
33 email: "[email protected]",
34 password: "a-good-passphrase",
35 adminIfFirst: true,
36 });
37 if (!u.ok) throw new Error(u.error);
38 proveAll(app);
39 const made = await createRepo(app, { id: u.value.id, username: "alice" } as never, { name: "forge" });
40 if (!made.ok) throw new Error(made.error);
41 const repoId = app.db.query<{ id: string }, []>("SELECT id FROM repo WHERE name='forge'").get()!.id;
42 for (const [i, s] of [LIVE, ...KEPT].entries())
43 recordRelease(app.db, { sha: s, repoId, pullNumber: i + 1, ok: true, line: "ok" });
44 for (const s of KEPT) {
45 const dir = releaseDir(root, s);
46 mkdirSync(join(dir, "src", "core"), { recursive: true });
47 writeFileSync(join(dir, "src", "core", "schema.ts"), "export const migrations = " + JSON.stringify(Array(10_000).fill("SELECT 1")) + ";\n");
48 }
49 servers = await serve(app);
50 });
51 afterAll(() => {
52 if (was === undefined) delete process.env["FORGE_COMMIT"];
53 else process.env["FORGE_COMMIT"] = was;
54 servers.stop();
55 app.close();
56 rmSync(root, { recursive: true, force: true });
57 });
58
59 test("a writer's GET /releases names its phases and has a Roll back button per non-live release", async () => {
60 const signin = await fetch(`${servers.url}/signin`, {
61 method: "POST",
62 headers: { origin: servers.url },
63 body: new URLSearchParams({ username: "alice", password: "a-good-passphrase" }),
64 redirect: "manual",
65 });
66 const cookie = (signin.headers.get("set-cookie") ?? "").split(";")[0]!;
67 const res = await fetch(`${servers.url}/releases`, { headers: { cookie } });
68 const body = await res.text();
69 expect(res.status).toBe(200);
70 const parts = (res.headers.get("server-timing") ?? "").split(", ");
71 for (const name of ["app", "session", "releases", "rollback", "render"])
72 expect(parts.find((p) => p.startsWith(`${name};`))).toMatch(/^[a-z]+;dur=\d+\.\d$/);
73 expect(body.match(/Roll back/g)?.length).toBe(KEPT.length);
74 expect(body).not.toContain(`/releases/${LIVE}/rollback`);
75 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs