Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

Web: the Board reads pull requests in every repository its open items link

It read the first 20 repositories that had live sessions, so an item with an open pull request elsewhere stood a column early, and every item did when sessions could not be read. It reads each repository an open item is linked to, and says the board is partial when one cannot be read.

G1T-Session:
ses_01m56vafhge7ttrgs5288dpxvb
syntaqxcommitted Parent7fa5f4fBrowse files
3 files+45−120/3 viewed
+16−1
33
44 import type { Claim, ItemRepo, WorkItem, WorkspaceSession } from "@g1t/contracts";
55
6−import { boardOf, pullsByItem, shownStage, stageOf } from "./board.ts";
6+import { boardOf, pullRepos, pullsByItem, shownStage, stageOf } from "./board.ts";
77
88 const NOW = Date.parse("2026-10-17T12:00:00Z");
99 const DAY = 86_400_000;
115115 assert.equal(shownStage("nonsense", board), "progress");
116116 assert.equal(shownStage(null, boardOf([], {}, NOW)), "backlog");
117117 });
118+
119+test("the board reads open pull requests in every repository an open work item is linked to, each once", () => {
120+ const items = [
121+ { state: "open", repos: [{ path: "acme/web" }, { path: "acme/api" }] },
122+ { state: "open", repos: [{ path: "Acme/Web" }, { path: "acme/repo-21" }] },
123+ // A closed item's repositories are not read: Done does not need its pull requests.
124+ { state: "closed", repos: [{ path: "acme/old" }] },
125+ // A repository deleted before work items kept no path.
126+ { state: "open", repos: [{ path: "" }] },
127+ ];
128+ assert.deepEqual(pullRepos(items), ["acme/web", "acme/api", "acme/repo-21"]);
129+ // However many there are: none is left out past a limit.
130+ const many = Array.from({ length: 30 }, (_, index) => ({ state: "open", repos: [{ path: `acme/r${index}` }] }));
131+ assert.equal(pullRepos(many).length, 30);
132+});
+15−0
8787 return board;
8888 }
8989
90+/**
91+ * The repositories whose open pull requests the board reads: every one an
92+ * open work item is linked to, each once, so an item's pull request moves
93+ * it wherever it was opened. A repository deleted before work items, with
94+ * no path kept, has none to read.
95+ */
96+export function pullRepos(items: readonly { state: string; repos: readonly { path: string }[] }[]): string[] {
97+ const seen = new Map<string, string>();
98+ for (const item of items) {
99+ if (item.state !== "open") continue;
100+ for (const repo of item.repos) if (repo.path && !seen.has(repo.path.toLowerCase())) seen.set(repo.path.toLowerCase(), repo.path);
101+ }
102+ return [...seen.values()];
103+}
104+
90105 /** Each work item's pull requests' statuses, from pull requests that name their item. */
91106 export function pullsByItem(pulls: { item: number | null; status: PullStatus }[]): ItemPulls {
92107 const by: ItemPulls = {};
+14−11
1111 import { Hint } from "../../../components/ui/hint";
1212 import { RepoChips } from "../../../components/work-items";
1313 import { ItemIcon } from "../../../components/work-icons";
14−import { type Board, type BoardCard, STAGES, STAGE_INFO, type Stage, boardOf, pullsByItem, shownStage } from "../../../lib/board";
14+import { type Board, type BoardCard, STAGES, STAGE_INFO, type Stage, boardOf, pullRepos, pullsByItem, shownStage } from "../../../lib/board";
1515 import { cn } from "../../../lib/cn";
1616 import { page } from "../../../lib/meta";
1717 import { isFailed, part } from "../../../lib/partial";
2323 import { useWorkspaceLive } from "../../../lib/use-repo-live";
2424 import { itemPath, newItemPath, repoFilter, shortPath, workPath } from "../../../lib/work-items";
2525
26−/** How many repositories' open pull requests the board reads. */
27−const PULL_REPOS = 20;
28−
2926 export function meta({ params, location, ...args }: Route.MetaArgs) {
3027 const repo = new URLSearchParams(location.search).get("repo");
3128 return page({ location, ...args }, { title: `Board${repo ? ` · ${repo}` : ""} · Work · ${params.owner} · g1t` });
4643 part(work.listWorkspaceSessions(workspace, viewer, { shown: "live", sessionsOnly: true, repo: repoName ?? undefined, limit: 200 }), "Live sessions"),
4744 ]);
4845 const sessions = isFailed(live) || !live.ok ? [] : live.value.sessions;
49− // The open pull requests that name their work items, in the repositories looked in.
50− const repos = isFailed(live) || !live.ok ? [] : live.value.repos.slice(0, PULL_REPOS);
46+ // The open pull requests that name their work items, in every repository an open item is linked to.
47+ const openItems = unwrap(open);
48+ const repos = pullRepos(openItems);
5149 const pulls = await Promise.all(
52− repos.map((one) => {
53− const [namespace = "", name = ""] = one.path.split("/");
50+ repos.map((path) => {
51+ const [namespace = "", name = ""] = path.split("/");
5452 return work.listPulls({ namespace, name }, viewer, "open").catch(() => null);
5553 }),
5654 );
5755 const openPulls: Pull[] = pulls.flatMap((found) => (found?.ok ? found.value : []));
56+ // A repository whose pull requests could not be read: its items may stand a column early, and the board says so.
57+ const unread = repos.filter((_, index) => !pulls[index]?.ok);
5858 const now = Date.now();
5959 const board = boardOf(
60− [...unwrap(open), ...(isFailed(closed) || !closed.ok ? [] : closed.value)],
60+ [...openItems, ...(isFailed(closed) || !closed.ok ? [] : closed.value)],
6161 { claims: isFailed(claims) || !claims.ok ? [] : claims.value, sessions, pulls: pullsByItem(openPulls) },
6262 now,
6363 );
6868 repo: repoName,
6969 board,
7070 stage: shownStage(query.get("stage"), board),
71− unavailable: [closed, claims, live].filter(isFailed).map((failed) => failed.what),
71+ unavailable: [
72+ ...[closed, claims, live].filter(isFailed).map((failed) => failed.what),
73+ ...(unread.length === 0 ? [] : [unread.length === 1 ? `Pull requests in ${unread[0]}` : `Pull requests in ${unread.length} repositories`]),
74+ ],
7275 live: sessions.length,
7376 };
7477 }
193196 </ButtonLink>
194197 )}
195198 </div>
196− {unavailable.length > 0 && <p className="mt-3 text-xs text-warn">{unavailable.join(", ")} could not be loaded just now; the board may be missing some of it.</p>}
199+ {unavailable.length > 0 && <p className="mt-3 text-xs text-warn">{unavailable.join(", ")} could not be loaded just now, so this board is partial: some work items may be missing, or stand a column early.</p>}
197200
198201 {total === 0 ? (
199202 <div className="mt-5">

Comments

Sign in to comment.