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

A pure read says who opened the last hour's pulls: per author, how many, and how many of those serve a flow issue #6449

Merged opened this

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

Merged

scout/forge:scout/6443-a-pure-read-says-who-opened-the-last-hou → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/6443-a-pure-read-says-who-opened-the-last-hou was merged into main as 2daef23.

Review

Sign in to review this change.
2 files changed +154 −0
src/flow/openers.ts added +60
@@ -0,0 +1,60 @@
1 /**
2 * Who opened the last hour's pulls (#6443): per author, how many pulls opened
3 * in the hour before `now`, and how many of those serve a flow issue. A pull
4 * serves flow when the issue its description closes (`Closes #N`) is one of
5 * the flow issues, or waits on one in the dependency graph, however far.
6 * Most first, ties by name. Same `publicOnly` rule as `readVitals`; two reads.
7 */
8
9 import type { Database } from "bun:sqlite";
10 import { closesFromBody } from "../core/pulls.ts";
11
12 const HOUR_MS = 3_600_000;
13
14 /** The flow issues of #5930: flow itself, the freeze, the steward's probe. */
15 export const FLOW_ISSUES: readonly number[] = [5930, 6217, 5994];

Hard-coded flow list: when the direction changes (e.g. #6263 is also buildable), this silently goes stale. Share one list with the alarms.

16
17 export interface Opener {
18 readonly author: string;
19 readonly opened: number;
20 readonly flow: number;
21 }
22
23 interface PullRow {
24 author: string;
25 repo_id: string;
26 body: string;
27 }
28
29 export function readOpeners(db: Database, now = new Date(), publicOnly = false): Opener[] {
30 const to = now.toISOString();
31 const from = new Date(now.getTime() - HOUR_MS).toISOString();
32 const pub = publicOnly ? 1 : 0;
33 const flowRows = db
34 .query<{ key: string }, [number, number, number, number]>(
35 `WITH RECURSIVE flow(id) AS (
36 SELECT i.id FROM issue i JOIN repo r ON r.id = i.repo_id
37 WHERE i.number IN (?1, ?2, ?3) AND (?4 = 0 OR r.visibility = 'public')
38 UNION
39 SELECT d.issue_id FROM issue_dependency d JOIN flow f ON f.id = d.blocked_by_id
40 )
41 SELECT i.repo_id || '#' || i.number AS key FROM issue i JOIN flow ON flow.id = i.id`,
42 )
43 .all(FLOW_ISSUES[0]!, FLOW_ISSUES[1]!, FLOW_ISSUES[2]!, pub);
44 const flow = new Set(flowRows.map((r) => r.key));
45 const pulls = db
46 .query<PullRow, [string, string, number]>(
47 `SELECT u.username AS author, i.repo_id AS repo_id, i.body AS body
48 FROM pull p JOIN issue i ON i.id = p.issue_id JOIN repo r ON r.id = i.repo_id JOIN user u ON u.id = i.author_id
49 WHERE i.created_at >= ?1 AND i.created_at <= ?2 AND (?3 = 0 OR r.visibility = 'public')`,
50 )
51 .all(from, to, pub);
52 const by = new Map<string, { opened: number; flow: number }>();
53 for (const p of pulls) {
54 const t = by.get(p.author) ?? { opened: 0, flow: 0 };
55 t.opened++;
56 if (closesFromBody(p.body).some((n) => flow.has(`${p.repo_id}#${n}`))) t.flow++;
57 by.set(p.author, t);
58 }
59 return [...by].map(([author, t]) => ({ author, ...t })).sort((a, b) => b.opened - a.opened || a.author.localeCompare(b.author));
60 }
test/flow-openers.test.ts added +94
@@ -0,0 +1,94 @@
1 /** Who opened the last hour's pulls (#6443). */
2 import { afterEach, beforeEach, 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 { createUser } from "../src/core/users.ts";
8 import { id } from "../src/kernel/id.ts";
9 import { readOpeners } from "../src/flow/openers.ts";
10
11 const NOW = new Date("2026-10-26T12:00:00.000Z");
12 const ago = (min: number) => new Date(NOW.getTime() - min * 60_000).toISOString();
13
14 describe("readOpeners", () => {
15 let dir: string;
16 let app: App;
17 let users: Record<string, string>;
18 let pub: string;
19 let priv: string;
20 let n = 0;
21
22 const repo = (name: string, visibility: string, owner: string) => {
23 const rid = id();
24 app.db.query(`INSERT INTO repo (id, owner_id, name, visibility) VALUES (?1, ?2, ?3, ?4)`).run(rid, owner, name, visibility);
25 return rid;
26 };
27 const issue = (repoId: string, number: number, author: string, kind = "issue", body = "", at = ago(600)) => {
28 const iid = id();
29 app.db
30 .query(`INSERT INTO issue (id, repo_id, number, kind, title, body, author_id, created_at) VALUES (?1, ?2, ?3, ?4, 't', ?5, ?6, ?7)`)
31 .run(iid, repoId, number, kind, body, author, at);
32 return iid;
33 };
34 const pull = (repoId: string, author: string, body: string, min: number) => {
35 const iid = issue(repoId, 100000 + ++n, author, "pull", body, ago(min));
36 app.db.query(`INSERT INTO pull (issue_id, base_branch, head_branch, base_oid, head_oid) VALUES (?1, 'main', ?2, '', '')`).run(iid, `b/${n}`);
37 };
38
39 beforeEach(async () => {
40 dir = mkdtempSync(join(tmpdir(), "forge-openers-"));
41 app = createApp({ dataDir: dir, port: 0 });
42 users = {};
43 for (const name of ["alice", "bob"]) {
44 const u = await createUser(app.db, { username: name, email: `${name}@forge.test`, password: "a-good-passphrase", origin: "seed", trusted: true });
45 if (!u.ok) throw new Error(u.error);
46 users[name] = u.value.id;
47 }
48 pub = repo("pub", "public", users.alice!);
49 priv = repo("priv", "private", users.alice!);
50 });
51 afterEach(() => {
52 app.db.close();
53 rmSync(dir, { recursive: true, force: true });
54 });
55
56 test("counts two authors, most first, and marks the pulls that serve a flow issue", () => {
57 issue(pub, 5930, users.alice!);
58 pull(pub, users.bob!, "Closes #5930", 5);
59 pull(pub, users.bob!, "Closes #1", 10);
60 pull(pub, users.bob!, "", 15);
61 pull(pub, users.alice!, "Fixes #6217", 20);
62 issue(pub, 6217, users.alice!);
63 expect(readOpeners(app.db, NOW)).toEqual([
64 { author: "bob", opened: 3, flow: 1 },
65 { author: "alice", opened: 1, flow: 1 },
66 ]);
67 });
68
69 test("a pull whose issue waits on a flow issue, however far, is flow", () => {
70 const root = issue(pub, 5994, users.alice!);
71 const mid = issue(pub, 20, users.alice!);
72 const leaf = issue(pub, 21, users.alice!);
73 issue(pub, 22, users.alice!);
74 const dep = (a: string, b: string) => app.db.query(`INSERT INTO issue_dependency (issue_id, blocked_by_id) VALUES (?1, ?2)`).run(a, b);
75 dep(mid, root);
76 dep(leaf, mid);
77 pull(pub, users.bob!, "Closes #21", 3);
78 pull(pub, users.bob!, "Closes #22", 4);
79 expect(readOpeners(app.db, NOW)).toEqual([{ author: "bob", opened: 2, flow: 1 }]);
80 });
81
82 test("a pull opened 61 minutes ago is left out, 59 is in", () => {
83 pull(pub, users.bob!, "", 61);
84 pull(pub, users.alice!, "", 59);
85 expect(readOpeners(app.db, NOW)).toEqual([{ author: "alice", opened: 1, flow: 0 }]);
86 });
87
88 test("publicOnly hides private repositories", () => {
89 pull(pub, users.alice!, "", 5);
90 pull(priv, users.bob!, "", 5);
91 expect(readOpeners(app.db, NOW).map((o) => o.author)).toEqual(["alice", "bob"]);
92 expect(readOpeners(app.db, NOW, true)).toEqual([{ author: "alice", opened: 1, flow: 0 }]);
93 });
94 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs