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

The feed and the inbox reads name their Server-Timing phases: session, feed or asks, render #5158

Merged opened this

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

Merged

scout/forge:scout/5155-the-feed-and-the-inbox-reads-name-their → main 1 commit fork

  • Merged. scout/5155-the-feed-and-the-inbox-reads-name-their was merged into main as 460011d.

Review

Sign in to review this change.
2 files changed +78 −7
src/http/feed.ts +16 −7
@@ -37,6 +37,7 @@ import {
3737 previewAsksBulk,
3838 } from "../actions/asks_bulk.ts";
3939 import type { FleetError } from "../actions/fleet.ts";
40 import { notePhase, takeSessionTime, timePhase } from "../core/route_timing.ts";
4041 import type { Handler } from "./context.ts";
4142 import type { Router } from "./router.ts";
4243 import {
@@ -51,6 +52,7 @@ import {
5152
5253 export function feedRoutes(router: Router<Handler>): void {
5354 router.add("GET", "/api/v1/feed", (ctx) => {
55 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
5456 const viewer = ctx.auth.user;
5557 if (!viewer) return unauthorized();
5658 const q = ctx.url.searchParams;
@@ -65,13 +67,14 @@ export function feedRoutes(router: Router<Handler>): void {
6567 const cursor = rawCursor === null ? undefined : parseCursor(rawCursor);
6668 if (rawCursor !== null && cursor === undefined)
6769 return apiError("invalid_cursor", 422, "that cursor was not issued here");
68 return json(
70 const page = timePhase(ctx.request, "feed", () =>
6971 readFeed(ctx.app.db, viewer, {
7072 ...(limit === undefined ? {} : { limit }),
7173 ...(cursor === undefined ? {} : { cursor }),
7274 }),
73 200,
74 { "cache-control": "private, no-store" },
75 );
76 return timePhase(ctx.request, "render", () =>
77 json(page, 200, { "cache-control": "private, no-store" }),
7578 );
7679 });
7780
@@ -87,8 +90,12 @@ export function feedRoutes(router: Router<Handler>): void {
8790 };
8891 };
8992 router.add("GET", "/api/v1/feed/chips", (ctx) => {
93 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
9094 if (!ctx.auth.user) return unauthorized();
91 return json(chipsAnswer(ctx), 200, { "cache-control": "private, no-store" });
95 const answer = timePhase(ctx.request, "chips", () => chipsAnswer(ctx));
96 return timePhase(ctx.request, "render", () =>
97 json(answer, 200, { "cache-control": "private, no-store" }),
98 );
9299 });
93100 router.add("POST", "/api/v1/feed/chips", async (ctx) => {
94101 const denied = requireScope(ctx, "rw");
@@ -111,11 +118,13 @@ export function feedRoutes(router: Router<Handler>): void {
111118
112119 // What asks the viewer, ranked, with the week's measure (`asksAnswer`).
113120 router.add("GET", "/api/v1/asks", (ctx) => {
121 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
114122 const viewer = ctx.auth.user;
115123 if (!viewer) return unauthorized();
116 return json(asksAnswer(ctx, viewer), 200, {
117 "cache-control": "private, no-store",
118 });
124 const answer = timePhase(ctx.request, "asks", () => asksAnswer(ctx, viewer));
125 return timePhase(ctx.request, "render", () =>
126 json(answer, 200, { "cache-control": "private, no-store" }),
127 );
119128 });
120129
121130 // Many asks, one action (`actions/asks_bulk.ts`): a preview by default,
test/feed-timing.test.ts added +62
@@ -0,0 +1,62 @@
1 /** The feed, its chips and the asks inbox name their phases in Server-Timing. */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtempSync, rmSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createUser } from "../src/core/users.ts";
8 import { serve, type Servers } from "../src/http/server.ts";
9 import { proveAll } from "./helpers/verified.ts";
10 import { quietLogs } from "./helpers/globals.ts";
11
12 quietLogs();
13
14 let root = "";
15 let app: App;
16 let servers: Servers;
17
18 beforeAll(async () => {
19 root = mkdtempSync(join(tmpdir(), "forge-feed-timing-"));
20 app = createApp({ dataDir: root, port: 0 });
21 const u = await createUser(app.db, {
22 origin: "seed",
23 trusted: true,
24 username: "alice",
25 email: "[email protected]",
26 password: "a-good-passphrase",
27 adminIfFirst: true,
28 });
29 if (!u.ok) throw new Error(u.error);
30 proveAll(app);
31 servers = await serve(app);
32 });
33 afterAll(() => {
34 servers.stop();
35 app.close();
36 rmSync(root, { recursive: true, force: true });
37 });
38
39 const signIn = async () => {
40 const res = await fetch(`${servers.url}/signin`, {
41 method: "POST",
42 headers: { origin: servers.url },
43 body: new URLSearchParams({ username: "alice", password: "a-good-passphrase" }),
44 redirect: "manual",
45 });
46 return (res.headers.get("set-cookie") ?? "").split(";")[0]!;
47 };
48
49 for (const [path, phase] of [
50 ["/api/v1/feed", "feed"],
51 ["/api/v1/feed/chips", "chips"],
52 ["/api/v1/asks", "asks"],
53 ] as const) {
54 test(`GET ${path} names session, ${phase} and render`, async () => {
55 const res = await fetch(`${servers.url}${path}`, { headers: { cookie: await signIn() } });
56 await res.arrayBuffer();
57 expect(res.status).toBe(200);
58 expect(res.headers.get("cache-control")).toBe("private, no-store");
59 const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]);
60 expect(names.filter((n) => ["session", phase, "render"].includes(n!))).toEqual(["session", phase, "render"]);
61 });
62 }

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs