Public: anyone can read this; the crew can change it.
A repository's issues list's Server-Timing names its phases: session, each read, render #3986
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3322-a-repository-s-issues-list-s-server-timi → main 2 commits fork 1 thread · 1 unresolved
2 files
changed +38
−1
src/web/studio/lists.ts +13 −1
| @@ -334,11 +334,13 @@ function listRead(ctx: Ctx, repo: Repo, f: ListFacets) { | ||
| 334 | 334 | export async function tellsList(ctx: Ctx): Promise<Response> { |
| 335 | 335 | const repo = loadRepo(ctx); |
| 336 | 336 | if (!repo) return notFoundPage(ctx); |
| 337 | notePhase(ctx.request, "session", takeSessionTime(ctx.request)); | |
| 337 | 338 | const db = ctx.app.db; |
| 338 | 339 | const base = repoBase(repo); |
| 339 | 340 | const f = readFacets(ctx); |
| 340 | 341 | const { options, record, wordCtx } = listRead(ctx, repo, f); |
| 341 | 342 | |
| 343 | let at = performance.now(); | |
| 342 | 344 | const fetched = listIssues(db, repo.id, { |
| 343 | 345 | ...options, |
| 344 | 346 | kind: "issue", |
| @@ -346,7 +348,11 @@ export async function tellsList(ctx: Ctx): Promise<Response> { | ||
| 346 | 348 | }).filter( |
| 347 | 349 | (issue) => issue.id !== repo.directionIssueId, |
| 348 | 350 | ); |
| 351 | notePhase(ctx.request, "issues", performance.now() - at); | |
| 352 | at = performance.now(); | |
| 349 | 353 | const closingPulls = openPullsClosing(db, repo.id); |
| 354 | notePhase(ctx.request, "closing", performance.now() - at); | |
| 355 | at = performance.now(); | |
| 350 | 356 | const words = await Promise.all( |
| 351 | 357 | fetched.map( |
| 352 | 358 | async (issue) => |
| @@ -354,8 +360,12 @@ export async function tellsList(ctx: Ctx): Promise<Response> { | ||
| 354 | 360 | closedWord(issue), |
| 355 | 361 | ), |
| 356 | 362 | ); |
| 363 | notePhase(ctx.request, "words", performance.now() - at); | |
| 364 | at = performance.now(); | |
| 357 | 365 | const labels = labelsForIssues(db, fetched.map((issue) => issue.id)); |
| 358 | 366 | const repoLabels = listLabels(db, repo.id); |
| 367 | notePhase(ctx.request, "labels", performance.now() - at); | |
| 368 | at = performance.now(); | |
| 359 | 369 | const rows = fetched |
| 360 | 370 | .map((issue, i) => ({ issue, info: words[i]! })) |
| 361 | 371 | .filter((r) => shown(r.info.word, f)); |
| @@ -453,7 +463,7 @@ export async function tellsList(ctx: Ctx): Promise<Response> { | ||
| 453 | 463 | </div> |
| 454 | 464 | ${liveMount({ repo: base })}`; |
| 455 | 465 | |
| 456 | return page({ | |
| 466 | const response = page({ | |
| 457 | 467 | ctx, |
| 458 | 468 | title: pageTitle("tells", repo, record !== undefined), |
| 459 | 469 | crumbs: crumbsFor(repo), |
| @@ -462,6 +472,8 @@ export async function tellsList(ctx: Ctx): Promise<Response> { | ||
| 462 | 472 | styleCss: labelCss([...repoLabels, ...[...labels.values()].flat()]), |
| 463 | 473 | scriptJs: liveClientJs, |
| 464 | 474 | }); |
| 475 | notePhase(ctx.request, "render", performance.now() - at); | |
| 476 | return response; | |
| 465 | 477 | } |
| 466 | 478 | |
| 467 | 479 | /** |
test/issues-list-timing.test.ts added +25
| @@ -0,0 +1,25 @@ | ||
| 1 | /** A repository's issues list names its Server-Timing phases in order: session, issues, closing, words, labels, render. */ | |
| 2 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 3 | import { snappyFixture, type SnappyFixture } from "../bench/snappy.ts"; | |
| 4 | ||
| 5 | let f: SnappyFixture; | |
| 6 | ||
| 7 | beforeAll(async () => { | |
| 8 | f = await snappyFixture(); | |
| 9 | }, 120_000); | |
| 10 | afterAll(async () => { | |
| 11 | await f.close(); | |
| 12 | }); | |
| 13 | ||
| 14 | for (const who of ["owner", "anonymous"] as const) { | |
| 15 | test(`${who}: the issues list names its phases in order`, async () => { | |
| 16 | const repo = f.repos[0]!; | |
| 17 | const res = await fetch(`${f.base}/${repo.ownerName}/${repo.name}/issues`, { | |
| 18 | headers: who === "owner" ? { cookie: f.ownerCookie } : {}, | |
| 19 | }); | |
| 20 | await res.arrayBuffer(); | |
| 21 | expect(res.status).toBe(200); | |
| 22 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]!); | |
| 23 | expect(names.filter((n) => n !== "app")).toEqual(["session", "issues", "closing", "words", "labels", "render"]); | |
| 24 | }); | |
| 25 | } |
new above
offline
render starts at the rows mapping, so it covers row building plus page(). Fine, but name it or split a rows phase if you want the number to mean one thing.