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

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

Merged opened this

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

Merged

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

  • Merged. scout/3445-the-model-settings-page-s-server-timing was merged into main as 5ffb9cb.

Review

Sign in to review this change.
2 files changed +54 −3
src/web/pages/model.ts +9 −3
@@ -17,6 +17,7 @@ import {
1717 import { API_BASE_MAX } from "../../core/subscriptions.ts";
1818 import type { User } from "../../core/users.ts";
1919 import type { Ctx } from "../../http/context.ts";
20 import { notePhase, takeSessionTime, timePhase } from "../../core/route_timing.ts";

Nit: this ../../core import sits after ../../http/context; group it with the other core imports above.

2021 import { notFoundPage, settingsLayout, signInFirst } from "../chrome.ts";
2122 import { field, readForm } from "../form.ts";
2223 import { html, type Html } from "../html.ts";
@@ -29,7 +30,10 @@ export function modelPage(ctx: Ctx): Response {
2930 const denied = accountOnly(ctx);
3031 if (denied) return denied;
3132 const user = ctx.auth.user!;
32 const own = ownSubscription(ctx.app.db, user.id);
33 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
34 const own = timePhase(ctx.request, "subscription", () => ownSubscription(ctx.app.db, user.id));
35 const budget = timePhase(ctx.request, "budget", () => budgetLine(ctx.app.db, user, new Date()));
36 const rendering = performance.now();
3337 const form = own
3438 ? html`<p class="muted">
3539 Your subscription <strong>${own.name}</strong> pays for the crew on your
@@ -53,7 +57,7 @@ export function modelPage(ctx: Ctx): Response {
5357 </div>
5458 <div>${btn("Add", { variant: "primary", size: "sm" })}</div>
5559 </form>`;
56 return page({
60 const response = page({
5761 ctx,
5862 title: "Model",
5963 body: html`<div class="page-head"><h1>Model</h1></div>
@@ -62,12 +66,14 @@ export function modelPage(ctx: Ctx): Response {
6266 card(html`${form}`, {
6367 head: html`<h2>Your model budget</h2>
6468 <p class="muted" id="model-budget">
65 ${budgetLine(ctx.app.db, user, new Date())}
69 ${budget}
6670 </p>`,
6771 }),
6872 "Account settings",
6973 )}`,
7074 });
75 notePhase(ctx.request, "render", performance.now() - rendering);
76 return response;
7177 }
7278
7379 // POST /settings/model
test/model-timing.test.ts added +45
@@ -0,0 +1,45 @@
1 /** The model settings page names its phases in Server-Timing (#3445). */
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-mtime-"));
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 model page names session, subscription, budget and render once each", async () => {
34 const res = await fetch(`${servers.url}/settings/model`, { headers: { authorization: `Bearer ${bearer}` } });
35 const page = await res.text();
36 expect(res.status).toBe(200);
37 expect(page).toContain("Your model budget");
38 const parts = (res.headers.get("server-timing") ?? "").split(", ");
39 const names = parts.map((p) => p.split(";")[0]!);
40 for (const n of ["session", "subscription", "budget", "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