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

GET …/runs/{id} names its Server-Timing phases: session, run, render #5333

Merged opened this

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

Merged

scout/forge:scout/5316-get-runs-id-names-its-server-timing-phas → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/5316-get-runs-id-names-its-server-timing-phas was merged into main as 13a6da5.

Review

Sign in to review this change.
2 files changed +25 −6
src/http/api.ts +7 −6
@@ -6126,15 +6126,16 @@ export function apiRoutes(router: Router<Handler>): void {
61266126 });
61276127
61286128 router.add("GET", "/api/v1/repos/:owner/:name/runs/:id", (ctx) => {
6129 notePhase(ctx.request, "session", takeSessionTime(ctx.request));
61296130 const repo = loadRepo(ctx);
61306131 if (!repo) return notFound();
6131 const run = getRepoRun(ctx.app, ctx.auth.user, repo, ctx.params.id ?? "");
6132 if (!run.ok) return notFound();
6133 // The merged pulls a finished production deploy carries (`core/carried.ts`).
6134 return json({
6135 ...run.value,
6136 carries: carriedBy(ctx.app.db, run.value.id),
6132 const found = timePhase(ctx.request, "run", () => {
6133 const run = getRepoRun(ctx.app, ctx.auth.user, repo, ctx.params.id ?? "");

Good: the ok check and carriedBy both sit inside run, so the phase measures the whole read and a miss never reaches the carried query.

6134 // The merged pulls a finished production deploy carries (`core/carried.ts`).
6135 return run.ok ? { ...run.value, carries: carriedBy(ctx.app.db, run.value.id) } : null;
61376136 });
6137 if (!found) return notFound();
6138 return timePhase(ctx.request, "render", () => json(found));
61386139 });
61396140
61406141 // The log: text, or a live tail as server-sent events (`?follow=1`, or
test/api-runs-timing.test.ts +18
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os";
55 import { join } from "node:path";
66 import { createApp, type App } from "../src/app.ts";
77 import { createRepo } from "../src/actions/repos.ts";
8 import { createRun } from "../src/core/runs.ts";
89 import { createSession } from "../src/core/sessions.ts";
910 import { createUser } from "../src/core/users.ts";
1011 import { serve, type Servers } from "../src/http/server.ts";
@@ -16,6 +17,7 @@ let dataDir: string;
1617 let app: App;
1718 let servers: Servers;
1819 let cookie = "";
20 let runId = "";
1921
2022 const cells = process.env["FORGE_CELLS"];
2123
@@ -37,6 +39,7 @@ beforeAll(async () => {
3739 cookie = `forge_session=${createSession(app.db, alice.id).token}`;
3840 const repo = await createRepo(app, alice, { name: "r", visibility: "public" });
3941 if (!repo.ok) throw new Error(String(repo.error));
42 runId = createRun(app.db, { repoId: repo.value.id, kind: "deploy", env: "production" }).id;
4043 });
4144 afterAll(async () => {
4245 await servers.stop();
@@ -65,3 +68,18 @@ test("a missing repository is a 404 naming session", async () => {
6568 expect(got.status).toBe(404);
6669 expect(got.names).toEqual(["session"]);
6770 });
71
72 test("one run's read names session, run, render in order, answer unchanged", async () => {
73 const got = await phases(`/api/v1/repos/alice/r/runs/${runId}`);
74 expect(got.status).toBe(200);
75 expect(got.names).toEqual(["session", "run", "render"]);
76 const body = JSON.parse(got.body);
77 expect(body.id).toBe(runId);
78 expect(body.carries).toEqual([]);
79 });
80
81 test("an unknown run is a 404 naming session, run", async () => {
82 const got = await phases("/api/v1/repos/alice/r/runs/nope");
83 expect(got.status).toBe(404);
84 expect(got.names).toEqual(["session", "run"]);
85 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs