Public: anyone can read this; the crew can change it.
A pure read counts the open pulls whose check is red, and those queued for a check, as two flow numbers #6431
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6423-a-pure-read-counts-the-open-pulls-whose → main 1 commit fork
2 files
changed +142
−0
src/flow/checks_pile.ts added +30
| @@ -0,0 +1,30 @@ | ||
| 1 | /** | |
| 2 | * Open pulls whose check is red, and those with a check waiting for a slot | |
| 3 | * (#5930). Read from the same check runs the pulls list words its status | |
| 4 | * from: only a run of the pull's current head counts, newest first. | |
| 5 | */ | |
| 6 | ||
| 7 | import type { Database } from "bun:sqlite"; | |
| 8 | ||
| 9 | export interface ChecksPile { | |
| 10 | /** Open, unmerged pulls whose latest check on the current head failed. */ | |
| 11 | readonly red: number; | |
| 12 | /** Open, unmerged pulls with a check on the current head queued for a slot. */ | |
| 13 | readonly queued: number; | |
| 14 | } | |
| 15 | ||
| 16 | /** `publicOnly` counts public repositories alone, as `readVitals` does. */ | |
| 17 | export function readChecksPile(db: Database, publicOnly = false): ChecksPile { | |
| 18 | const row = db | |
| 19 | .query<{ red: number | null; queued: number | null }, [number]>( | |
| 20 | `SELECT SUM(latest = 'failed') AS red, SUM(waiting) AS queued FROM ( | |
| 21 | SELECT (SELECT c.state FROM run c WHERE c.kind = 'check' AND c.issue_id = i.id | |
| 22 | AND c.commit_oid = p.head_oid ORDER BY c.id DESC LIMIT 1) AS latest, | |
| 23 | EXISTS (SELECT 1 FROM run c WHERE c.kind = 'check' AND c.issue_id = i.id | |
| 24 | AND c.commit_oid = p.head_oid AND c.state = 'queued') AS waiting | |
| 25 | FROM issue i JOIN pull p ON p.issue_id = i.id JOIN repo r ON r.id = i.repo_id | |
| 26 | WHERE i.state = 'open' AND p.merged_at IS NULL AND (?1 = 0 OR r.visibility = 'public'))`, | |
| 27 | ) | |
| 28 | .get(publicOnly ? 1 : 0); | |
| 29 | return { red: row?.red ?? 0, queued: row?.queued ?? 0 }; | |
| 30 | } |
test/flow-checks-pile.test.ts added +112
| @@ -0,0 +1,112 @@ | ||
| 1 | /** The checks pile (#5930): open pulls red on their current head, and those with a check queued. */ | |
| 2 | import { afterAll, beforeAll, describe, 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 { createRepo } from "../src/core/repos.ts"; | |
| 8 | import { createUser } from "../src/core/users.ts"; | |
| 9 | import { id } from "../src/kernel/id.ts"; | |
| 10 | import { readChecksPile } from "../src/flow/checks_pile.ts"; | |
| 11 | ||
| 12 | let root: string; | |
| 13 | let app: App; | |
| 14 | let ownerId: string; | |
| 15 | let pub: string; | |
| 16 | let priv: string; | |
| 17 | let n = 0; | |
| 18 | let seq = 0; | |
| 19 | ||
| 20 | beforeAll(async () => { | |
| 21 | root = mkdtempSync(join(tmpdir(), "forge-checks-pile-")); | |
| 22 | app = createApp({ dataDir: root, port: 0 }); | |
| 23 | const made = await createUser(app.db, { username: "trevato", email: "[email protected]", password: "a-good-passphrase", origin: "seed", trusted: true, adminIfFirst: true }); | |
| 24 | if (!made.ok) throw new Error(made.error); | |
| 25 | ownerId = made.value.id; | |
| 26 | for (const [name, visibility] of [["open", "public"], ["secret", "private"]] as const) { | |
| 27 | const repo = await createRepo(app.db, app.reposRoot, { ownerId, name, visibility, description: "r" }); | |
| 28 | if (!repo.ok) throw new Error(repo.error); | |
| 29 | if (name === "open") pub = repo.value.id; | |
| 30 | else priv = repo.value.id; | |
| 31 | } | |
| 32 | }); | |
| 33 | afterAll(() => { | |
| 34 | app.close(); | |
| 35 | rmSync(root, { recursive: true, force: true }); | |
| 36 | }); | |
| 37 | ||
| 38 | function pull(repoId: string, head: string, opts: { merged?: boolean } = {}): string { | |
| 39 | const issue = id(); | |
| 40 | app.db | |
| 41 | .query(`INSERT INTO issue (id, repo_id, number, kind, title, body, author_id, state) VALUES (?1, ?2, ?3, 'pull', 't', '', ?4, ?5)`) | |
| 42 | .run(issue, repoId, ++n, ownerId, opts.merged ? "closed" : "open"); | |
| 43 | app.db | |
| 44 | .query(`INSERT INTO pull (issue_id, base_branch, head_branch, base_oid, head_oid, head_repo_id, merged_at) VALUES (?1, 'main', 'b', '', ?2, ?3, ?4)`) | |
| 45 | .run(issue, head, repoId, opts.merged ? "2026-10-26T00:00:00.000Z" : null); | |
| 46 | return issue; | |
| 47 | } | |
| 48 | function check(repoId: string, issue: string, oid: string, state: string): void { | |
| 49 | app.db | |
| 50 | .query(`INSERT INTO run (id, repo_id, kind, issue_id, commit_oid, state) VALUES (?1, ?2, 'check', ?3, ?4, ?5)`) | |
| 51 | .run(`${String(++seq).padStart(4, "0")}${id()}`, repoId, issue, oid, state); | |
| 52 | } | |
| 53 | const clear = (): void => { | |
| 54 | app.db.query(`DELETE FROM run`).run(); | |
| 55 | app.db.query(`DELETE FROM pull`).run(); | |
| 56 | app.db.query(`DELETE FROM issue`).run(); | |
| 57 | }; | |
| 58 | ||
| 59 | describe("checks pile", () => { | |
| 60 | test("nothing yet reads zero", () => { | |
| 61 | expect(readChecksPile(app.db)).toEqual({ red: 0, queued: 0 }); | |
| 62 | }); | |
| 63 | ||
| 64 | test("a red pull counts; red on an older head, green on the current, does not", () => { | |
| 65 | clear(); | |
| 66 | const a = pull(pub, "h1"); | |
| 67 | check(pub, a, "h1", "failed"); | |
| 68 | const b = pull(pub, "h2"); | |
| 69 | check(pub, b, "h1", "failed"); | |
| 70 | check(pub, b, "h2", "ok"); | |
| 71 | expect(readChecksPile(app.db)).toEqual({ red: 1, queued: 0 }); | |
| 72 | }); | |
| 73 | ||
| 74 | test("a red run on the old head does not count when the current head has none", () => { | |
| 75 | clear(); | |
| 76 | check(pub, pull(pub, "h2"), "h1", "failed"); | |
| 77 | expect(readChecksPile(app.db).red).toBe(0); | |
| 78 | }); | |
| 79 | ||
| 80 | test("a later green on the same head clears red", () => { | |
| 81 | clear(); | |
| 82 | const a = pull(pub, "h1"); | |
| 83 | check(pub, a, "h1", "failed"); | |
| 84 | check(pub, a, "h1", "ok"); | |
| 85 | expect(readChecksPile(app.db).red).toBe(0); | |
| 86 | }); | |
| 87 | ||
| 88 | test("a merged pull is not counted", () => { | |
| 89 | clear(); | |
| 90 | const a = pull(pub, "h1", { merged: true }); | |
| 91 | check(pub, a, "h1", "failed"); | |
| 92 | check(pub, a, "h1", "queued"); | |
| 93 | expect(readChecksPile(app.db)).toEqual({ red: 0, queued: 0 }); | |
| 94 | }); | |
| 95 | ||
| 96 | test("a queued check is counted once, even with two queued", () => { | |
| 97 | clear(); | |
| 98 | const a = pull(pub, "h1"); | |
| 99 | check(pub, a, "h1", "queued"); | |
| 100 | check(pub, a, "h1", "queued"); | |
| 101 | expect(readChecksPile(app.db)).toEqual({ red: 0, queued: 1 }); | |
| 102 | }); | |
| 103 | ||
| 104 | test("publicOnly hides private repositories", () => { | |
| 105 | clear(); | |
| 106 | check(priv, pull(priv, "p1"), "p1", "failed"); | |
| 107 | check(priv, pull(priv, "p2"), "p2", "queued"); | |
| 108 | check(pub, pull(pub, "h1"), "h1", "failed"); | |
| 109 | expect(readChecksPile(app.db)).toEqual({ red: 2, queued: 1 }); | |
| 110 | expect(readChecksPile(app.db, true)).toEqual({ red: 1, queued: 0 }); | |
| 111 | }); | |
| 112 | }); |
new above
offline