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

The cloud settings page's Server-Timing names its phases: session, each read, render #3472

Merged opened this

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

Merged

scout/forge:scout/3444-the-cloud-settings-page-s-server-timing → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/3444-the-cloud-settings-page-s-server-timing was merged into main as 7f9d0a4.

Review

Sign in to review this change.
2 files changed +59 −6
src/web/pages/cloud.ts +14 −6
@@ -21,8 +21,10 @@ import { auditToday } from "../../core/cloud_audit.ts";
2121 import {
2222 listCloudAccounts,
2323 recentCloudReads,
24 type CloudAccount,
2425 type CloudRead,
2526 } from "../../core/cloud_account.ts";
27 import { notePhase, takeSessionTime, timePhase, timePhaseAsync } from "../../core/route_timing.ts";
2628 import type { Ctx } from "../../http/context.ts";
2729 import { notFoundPage, settingsLayout, signInFirst } from "../chrome.ts";
2830 import { field, readForm } from "../form.ts";
@@ -47,8 +49,7 @@ const hidden = (provider: string, name: string): Html =>
4749 html`<input type="hidden" name="provider" value="${provider}" />
4850 <input type="hidden" name="name" value="${name}" />`;
4951
50 function accountsCard(reads: CloudRead[], uses: Map<string, VendorUse[]>, ctx: Ctx): Html {
51 const accounts = listCloudAccounts(ctx.app.db);
52 function accountsCard(accounts: CloudAccount[], reads: CloudRead[], uses: Map<string, VendorUse[]>): Html {
5253 const rows = accounts.map((a) => {
5354 const last = reads.find((r) => r.provider === a.provider && r.name === a.name);
5455 return row({
@@ -142,21 +143,28 @@ function readsCard(reads: CloudRead[]): Html {
142143
143144 // GET /settings/cloud
144145 export async function cloudPage(ctx: Ctx): Promise<Response> {
146 notePhase(ctx.request, "session", takeSessionTime(ctx.request));

Session is noted before the admin check, so a denied request still names its phase. Good.

145147 const denied = requireAdmin(ctx);
146148 if (denied) return denied;
147 const reads = recentCloudReads(ctx.app.db);
148 const uses = await usesByVendor(ctx.app.db, ctx.app.reposRoot, ctx.auth.user!);
149 return page({
149 const reads = timePhase(ctx.request, "reads", () => recentCloudReads(ctx.app.db));
150 const uses = await timePhaseAsync(ctx.request, "uses", () =>
151 usesByVendor(ctx.app.db, ctx.app.reposRoot, ctx.auth.user!),
152 );
153 const accounts = timePhase(ctx.request, "accounts", () => listCloudAccounts(ctx.app.db));
154 const rendering = performance.now();
155 const response = page({
150156 ctx,
151157 title: "Cloud accounts",
152158 body: html`<div class="page-head"><h1>Cloud accounts</h1></div>
153159 ${settingsLayout(
154160 accountSections("cloud", { admin: true }),
155 html`${accountsCard(reads, uses, ctx)}<div class="mt">${readsCard(reads)}</div>
161 html`${accountsCard(accounts, reads, uses)}<div class="mt">${readsCard(reads)}</div>
156162 <p class="muted small mt"><a href="/settings/cloud/audit">Every vendor call today</a></p>`,
157163 "Account settings",
158164 )}`,
159165 });
166 notePhase(ctx.request, "render", performance.now() - rendering);
167 return response;
160168 }
161169
162170 const said = (ctx: Ctx, r: { ok: boolean }, good: string, bad: string | undefined) =>
test/cloud-timing.test.ts added +45
@@ -0,0 +1,45 @@
1 /** The cloud settings page names its phases in Server-Timing (#3444). */
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 { serve, type Servers } from "../src/http/server.ts";
8 import { seedAdmin } from "./helpers/admin.ts";
9
10 let dataDir = "";
11 let app: App, servers: Servers, bearer = "";
12
13 beforeAll(async () => {
14 dataDir = await mkdtemp(join(tmpdir(), "forge-ctime-"));
15 app = createApp({ dataDir, port: 0 });
16 servers = await serve(app);
17 const admin = await seedAdmin(app, { username: "alice", email: "[email protected]", password: "correct-horse-battery" });
18 if (!admin.ok) throw new Error(admin.error);
19 const minted = await fetch(`${servers.url}/api/v1/tokens`, {
20 method: "POST",
21 headers: { "content-type": "application/json" },
22 body: JSON.stringify({ adminConfirm: true, username: "alice", password: "correct-horse-battery", name: "cli", scope: "admin" }),
23 });
24 bearer = ((await minted.json()) as { token: string }).token;
25 });
26
27 afterAll(async () => {
28 servers.stop();
29 app.close();
30 await rm(dataDir, { recursive: true, force: true });
31 });
32
33 test("the cloud settings page names session, each read and render once each", async () => {
34 const res = await fetch(`${servers.url}/settings/cloud`, { headers: { authorization: `Bearer ${bearer}` } });
35 const page = await res.text();
36 expect(res.status).toBe(200);
37 expect(page).toContain("Cloud accounts");
38 const parts = (res.headers.get("server-timing") ?? "").split(", ");
39 const names = parts.map((p) => p.split(";")[0]!);
40 for (const n of ["session", "reads", "uses", "accounts", "render"]) {
41 expect(names.filter((x) => x === n).length).toBe(1);
42 const part = parts.find((p) => p.startsWith(`${n};`))!;
43 expect(Number(/dur=([\d.]+)/.exec(part)?.[1])).not.toBeNaN();
44 }
45 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs