Public: anyone can read this; the crew can change it.
A map's share card resolves under 150 ms and draws under 300 KB for 2,000 closed and 300 open issues #6265
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6206-a-map-s-share-card-resolves-under-150-ms → main 1 commit fork
3 files
changed +31
−17
src/web/og/map.ts +17 −9
| @@ -103,7 +103,13 @@ export function mapStat(counts: Record<Word, number>): string { | ||
| 103 | 103 | } |
| 104 | 104 | |
| 105 | 105 | /** A repository's map card, from its graph as nobody reads it and its marks. */ |
| 106 | export function mapInput(title: string, graph: Graph, marks: Marks): CardInput { | |
| 106 | export function mapInput( | |
| 107 | title: string, | |
| 108 | graph: Graph, | |
| 109 | marks: Marks, | |
| 110 | /** Names a stretch of the work; the route times it, a test need not. */ | |
| 111 | stretch: <T>(name: string, work: () => T) => T = (_name, work) => work(), | |
| 112 | ): CardInput { | |
| 107 | 113 | const ready = readyNow(graph); |
| 108 | 114 | const path = criticalPath(graph); |
| 109 | 115 | const readySet = new Set(ready.map((r) => r.issue.id)); |
| @@ -111,14 +117,16 @@ export function mapInput(title: string, graph: Graph, marks: Marks): CardInput { | ||
| 111 | 117 | const open = graph.issues.filter((i) => i.state === "open").length; |
| 112 | 118 | // The thumbnail draws what is open: done issues are counted in the caption, not drawn. |
| 113 | 119 | const live = new Set(graph.issues.filter((i) => i.state === "open").map((i) => i.id)); |
| 114 | const figure = graphFigure( | |
| 115 | { | |
| 116 | ...graph, | |
| 117 | issues: graph.issues.filter((i) => live.has(i.id)), | |
| 118 | edges: graph.edges.filter((e) => live.has(e.issueId) && live.has(e.blockedById)), | |
| 119 | }, | |
| 120 | words, | |
| 121 | path.map((i) => i.id), | |
| 120 | const figure = stretch("layout", () => | |
| 121 | graphFigure( | |
| 122 | { | |
| 123 | ...graph, | |
| 124 | issues: graph.issues.filter((i) => live.has(i.id)), | |
| 125 | edges: graph.edges.filter((e) => live.has(e.issueId) && live.has(e.blockedById)), | |
| 126 | }, | |
| 127 | words, | |
| 128 | path.map((i) => i.id), | |
| 129 | ), | |
| 122 | 130 | ); |
| 123 | 131 | // Next is what a free hand would take: ready, and nobody on it yet. |
| 124 | 132 | const next = ready.find((r) => words.get(r.issue.id) === "ready")?.issue; |
src/web/pages/og.ts +10 −6
| @@ -291,12 +291,16 @@ function mapCardInput(ctx: Ctx, ref: string): CardInput | undefined { | ||
| 291 | 291 | const repo = twoPart(ctx, ref); |
| 292 | 292 | if (!repo) return undefined; |
| 293 | 293 | const db = ctx.app.db; |
| 294 | const graph = repoGraph(db, repo.id, sealedScope(db, repo, undefined)); | |
| 295 | // A run on a sealed report is not shown to who may not see the report. | |
| 296 | const runs = listActiveRuns(db, [repo.id]).filter((r) => | |
| 297 | mayViewId(db, repo, r.issueId, undefined), | |
| 298 | ); | |
| 299 | return mapInput(`${repo.ownerName}/${repo.name}`, graph, marksFor(db, repo.id, graph, runs)); | |
| 294 | const stretch = <T>(name: string, work: () => T): T => timePhase(ctx.request, name, work); | |
| 295 | const graph = stretch("graph", () => repoGraph(db, repo.id, sealedScope(db, repo, undefined))); | |
| 296 | const marks = stretch("marks", () => { | |
| 297 | // A run on a sealed report is not shown to who may not see the report. | |
| 298 | const runs = listActiveRuns(db, [repo.id]).filter((r) => | |
| 299 | mayViewId(db, repo, r.issueId, undefined), | |
| 300 | ); | |
| 301 | return marksFor(db, repo.id, graph, runs); | |
| 302 | }); | |
| 303 | return mapInput(`${repo.ownerName}/${repo.name}`, graph, marks, stretch); | |
| 300 | 304 | } |
| 301 | 305 | |
| 302 | 306 | /** The public apps, read the way the audit page reads them for someone signed out. */ |
test/og-map.test.ts +4 −2
| @@ -50,10 +50,12 @@ test("the card draws open issues only, names the done count, and resolves fast", | ||
| 50 | 50 | const res = await fetch(`${servers.url}/og/map/alice~notes.svg`); |
| 51 | 51 | const svg = await res.text(); |
| 52 | 52 | expect(res.status).toBe(200); |
| 53 | expect(new TextEncoder().encode(svg).length).toBeLessThan(200_000); | |
| 53 | expect(new TextEncoder().encode(svg).length).toBeLessThan(300_000); | |
| 54 | 54 | expect(svg).toContain("2000 done"); |
| 55 | 55 | expect(svg).toContain("300 open issues"); |
| 56 | 56 | const resolve = /resolve;dur=([\d.]+)/.exec(res.headers.get("server-timing") ?? ""); |
| 57 | 57 | expect(resolve).not.toBeNull(); |
| 58 | expect(Number(resolve![1])).toBeLessThan(100); | |
| 58 | expect(Number(resolve![1])).toBeLessThan(150); | |
| 59 | const names = (res.headers.get("server-timing") ?? "").split(", ").map((p) => p.split(";")[0]); | |
| 60 | for (const n of ["graph", "marks", "layout"]) expect(names).toContain(n); | |
| 59 | 61 | }); |
new above
offline