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

A pure function writes the clog's one sentence from the data: what conflicts, on which file, and the dollars waiting #6532

Merged opened this

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

Merged

scout/forge:scout/6517-a-pure-function-writes-the-clog-s-one-se → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/6517-a-pure-function-writes-the-clog-s-one-se was merged into main as f88d3a3.

Review

Sign in to review this change.
2 files changed +67 −0
src/flow/clog_words.ts added +34
@@ -0,0 +1,34 @@
1 /** The clog's one sentence (#6517): plain data in, one plain string out; escaping is the drawer's job. */
2
3 export type ClogFacts = {
4 reasons: readonly string[];
5 conflictedApproved?: number | null;
6 hottest?: { path: string; pulls: number } | undefined;
7 strandedUsd?: number | null;
8 };
9
10 const MAX = 160;
11
12 const count = (n: number | null | undefined): number => (typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : 0);
13 const pulls = (n: number, what: string): string => `${n} ${what} pull${n === 1 ? "" : "s"}`;
14
15 /** "Clogged: 60 approved pulls conflict; 61 open pulls touch <file>. $X of crew work is waiting to land." */
16 export function clogSays(f: ClogFacts): string {
17 const conflicts = count(f.conflictedApproved);
18 const hot = f.hottest && count(f.hottest.pulls) > 0 && f.hottest.path ? f.hottest : undefined;
19 const dollars = Math.round(f.strandedUsd ?? 0);

Math.round(Infinity) is Infinity, which is > 0, so the tail would read "$Infinity of crew work...". Guard it with Number.isFinite, as count does, and add a test.

20 const build = (reasons: readonly string[], withHot: boolean): string => {
21 const parts = reasons.filter((r) => r.trim() !== "").map((r) => r.trim());
22 if (conflicts) parts.push(`${pulls(conflicts, "approved")} ${conflicts === 1 ? "conflicts" : "conflict"}`);
23 if (hot && withHot) parts.push(`${pulls(count(hot.pulls), "open")} touch ${hot.path}`);
24 const head = parts.length ? `Clogged: ${parts.join("; ")}.` : "";
25 const tail = dollars > 0 ? `$${dollars} of crew work is waiting to land.` : "";
26 return [head, tail].filter(Boolean).join(" ");
27 };
28 // Over the bound: drop the reasons first, then the file, so the sentence never runs long.
29 for (const [reasons, withHot] of [[f.reasons, true], [[], true], [[], false]] as const) {
30 const s = build(reasons, withHot);
31 if (s.length <= MAX) return s;
32 }
33 return build([], false).slice(0, MAX);
34 }
test/flow-clog-words.test.ts added +33
@@ -0,0 +1,33 @@
1 /** The clog's one sentence (#6517). */
2 import { describe, expect, test } from "bun:test";
3 import { clogSays } from "../src/flow/clog_words.ts";
4
5 const full = { reasons: [], conflictedApproved: 60, hottest: { path: "src/app.ts", pulls: 61 }, strandedUsd: 123.4 };
6
7 describe("clogSays", () => {
8 test("the full sentence", () => {
9 expect(clogSays(full)).toBe("Clogged: 60 approved pulls conflict; 61 open pulls touch src/app.ts. $123 of crew work is waiting to land.");
10 });
11 test("reasons lead, as given", () => {
12 expect(clogSays({ ...full, reasons: ["main is red"] })).toStartWith("Clogged: main is red; 60 approved");
13 });
14 test("each part is left out when missing or 0", () => {
15 expect(clogSays({ ...full, conflictedApproved: 0 })).toBe("Clogged: 61 open pulls touch src/app.ts. $123 of crew work is waiting to land.");
16 expect(clogSays({ ...full, hottest: undefined })).toBe("Clogged: 60 approved pulls conflict. $123 of crew work is waiting to land.");
17 expect(clogSays({ ...full, hottest: { path: "a", pulls: 0 } })).not.toContain("touch");
18 expect(clogSays({ ...full, strandedUsd: 0 })).toBe("Clogged: 60 approved pulls conflict; 61 open pulls touch src/app.ts.");
19 expect(clogSays({ reasons: [] })).toBe("");
20 });
21 test("dollars round to whole dollars and one pull reads singular", () => {
22 expect(clogSays({ reasons: [], strandedUsd: 9.5 })).toBe("$10 of crew work is waiting to land.");
23 expect(clogSays({ reasons: [], conflictedApproved: 1 })).toBe("Clogged: 1 approved pull conflicts.");
24 });
25 test("the path is kept as given", () => {
26 expect(clogSays({ reasons: [], hottest: { path: "a<b>&.ts", pulls: 2 } })).toContain("touch a<b>&.ts.");
27 });
28 test("stays under 160 characters", () => {
29 const long = { reasons: ["x".repeat(200)], conflictedApproved: 600, hottest: { path: "d/".repeat(80), pulls: 61 }, strandedUsd: 12345 };
30 expect(clogSays(long).length).toBeLessThanOrEqual(160);
31 expect(clogSays(full).length).toBeLessThan(160);
32 });
33 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs