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

An ask that moved on and an unknown bulk operation are said from one module #5682

Merged opened this

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

Merged

mason/forge:mason/5654-an-ask-that-moved-on-and-an-unknown-bulk → main 2 commits fork 1 thread · 1 unresolved

  • Merged. mason/5654-an-ask-that-moved-on-and-an-unknown-bulk was merged into main as b767d1d.

Review

Sign in to review this change.
8 files changed +65 −10
src/actions/asks_bulk.ts +2 −1
@@ -12,6 +12,7 @@ import type { App } from "../app.ts";
1212 import { listOperations, type Operation } from "../core/fleet_ops.ts";
1313 import type { User } from "../core/users.ts";
1414 import { err, ok, type Result } from "../kernel/result.ts";
15 import { NO_SUCH_OPERATION } from "./asks_words.ts";
1516 import { askTargetsFor } from "../web/studio/asks.ts";
1617 import {
1718 applyFleetIds,
@@ -191,6 +192,6 @@ export async function undoAsksBulk(
191192 ): Promise<Result<Operation, FleetError>> {
192193 const op = ownAsksOperation(app, actor, operationId);
193194 if (op === undefined)
194 return err({ code: "not_found", message: "no such operation here" });
195 return err({ code: "not_found", message: NO_SUCH_OPERATION });
195196 return undoFleetOp(app, actor, op.id);
196197 }
src/actions/asks_decide.ts +2 −1
@@ -14,6 +14,7 @@ import type { User } from "../core/users.ts";
1414 import { guardedHand, letDeciderHandle, undoHandled } from "../crew/handover.ts";
1515 import { err, ok, type Result } from "../kernel/result.ts";
1616 import { askTargetsFor } from "../web/studio/asks.ts";
17 import { NO_LONGER_ASKS } from "./asks_words.ts";
1718 import type { FleetError } from "./fleet.ts";
1819 import { SIGN_IN_TO_ACT } from "./asks_words.ts";
1920
@@ -69,7 +70,7 @@ export async function decideAsk(
6970 const issue = issueIds[0] === undefined ? undefined : getIssueById(app.db, issueIds[0]);
7071 const repo = issue === undefined ? undefined : getRepoById(app.db, issue.repoId);
7172 if (issue === undefined || repo === undefined)
72 return fail("not_found", "it no longer asks you");
73 return fail("not_found", NO_LONGER_ASKS);
7374 const handed = await guardedHand(app, issue.id, now, () =>
7475 letDeciderHandle(app, actor, repo, issue, repo.decisionsBy),
7576 );
src/actions/asks_words.ts +11 −1
@@ -1,8 +1,18 @@
11 /**
22 * What acting on asks says, said from one place (#5614): bulk decide,
3 * decide and proposals read it here, so the web, the API, MCP and the
3 * decide and proposals read it here, and an ask or a bulk operation whose
4 * subject is gone answers from here too, so the web, the API, MCP and the
45 * terminal word it the same.
56 */
67
78 /** Acting on an ask while signed out. */
89 export const SIGN_IN_TO_ACT = "sign in to act on your asks";
10
11 /** Deciding an ask that has moved on: it no longer waits on this person. */
12 export const NO_LONGER_ASKS = "it no longer asks you";
13
14 /** A bulk operation, or a fleet verb, this place does not know. */
15 export const NO_SUCH_OPERATION = "no such operation here";
16
17 /** A milestone that is not here, or not readable by the one asking. */
18 export const NO_SUCH_MILESTONE = "no such milestone here";
src/actions/fleet.ts +3 −2
@@ -57,6 +57,7 @@ import {
5757 type FleetKind,
5858 } from "../fleet/selection.ts";
5959 import { VERBS, type Target, type Verb } from "../fleet/verbs.ts";
60 import { NO_SUCH_OPERATION } from "./asks_words.ts";
6061
6162 export interface FleetError {
6263 readonly code:
@@ -599,7 +600,7 @@ export async function undoFleetOp(
599600 op.actorId !== person.value.id &&
600601 (verb.personal || !parseQuery(op.query).ok)
601602 )
602 return fail("not_found", "no such operation here");
603 return fail("not_found", NO_SUCH_OPERATION);
603604 // The day is the same on every door: the card, the API, MCP.
604605 if (keepsADay(op) && Date.now() - Date.parse(op.createdAt) > UNDO_WINDOW_MS)
605606 return fail("expired", "an undo is kept for a day, and this one's day is over");
@@ -661,7 +662,7 @@ export function getFleetOp(
661662 ): Result<Operation, FleetError> {
662663 const op = getOperation(app.db, operationId);
663664 if (!op || !actor || (op.actorId !== actor.id && !actor.isAdmin))
664 return fail("not_found", "no such operation here");
665 return fail("not_found", NO_SUCH_OPERATION);
665666 return ok(op);
666667 }
667668
src/actions/limb_replan.ts +3 −2
@@ -22,6 +22,7 @@ import { canAdmin, canRead, type Repo } from "../core/repos.ts";
2222 import type { User } from "../core/users.ts";
2323 import { err, ok, type Result } from "../kernel/result.ts";
2424 import { getIssueById } from "../core/issues.ts";
25 import { NO_SUCH_MILESTONE } from "./asks_words.ts";
2526 import type { FleetError } from "./fleet.ts";
2627 import type { LimbIssue } from "./limbs.ts";
2728 import { IN_HAND } from "./plan_words.ts";
@@ -64,10 +65,10 @@ function find(
6465 number: number,
6566 ): Result<ReplanPreview, FleetError> {
6667 if (!actor) return fail("unauthorized", "sign in to hand a limb to the planner");
67 if (!canRead(app.db, repo, actor)) return fail("not_found", "no such milestone here");
68 if (!canRead(app.db, repo, actor)) return fail("not_found", NO_SUCH_MILESTONE);
6869 const issues = openBeneath(app.db, repo, number);
6970 const limb = issues.find((i) => i.number === number);
70 if (limb === undefined) return fail("not_found", "no such milestone here");
71 if (limb === undefined) return fail("not_found", NO_SUCH_MILESTONE);
7172 const pending = pendingFor(app.db, repo, limb.id);
7273 const blocked =
7374 mayAct(app, actor, repo)?.message ??
src/actions/limbs.ts +3 −2
@@ -34,6 +34,7 @@ import { parseSeat, seating } from "../core/limb_seats.ts";
3434 import { readPlan } from "../core/milestones.ts";
3535 import { isStructural, pillarsOf } from "../core/pillars.ts";
3636 import { canRead, type Repo } from "../core/repos.ts";
37 import { NO_SUCH_MILESTONE } from "./asks_words.ts";
3738 import type { User } from "../core/users.ts";
3839 import { err, ok, type Result } from "../kernel/result.ts";
3940 import {
@@ -165,11 +166,11 @@ function shape(
165166 if (!actor) return fail("unauthorized", "sign in to act on a limb");
166167 // One answer for no such repository and not yours to see.
167168 if (!canRead(app.db, repo, actor))
168 return fail("not_found", "no such milestone here");
169 return fail("not_found", NO_SUCH_MILESTONE);
169170 const plan = readPlan(app.db, repo);
170171 const rows = plan.limb(number);
171172 if (rows === undefined || rows.own[0] === undefined)
172 return fail("not_found", "no such milestone here");
173 return fail("not_found", NO_SUCH_MILESTONE);
173174 const limb = brief(rows.own[0]);
174175 const where = `issue repo:${repo.ownerName}/${repo.name}`;
175176 if (request.action === "raise" || request.action === "lower")
src/web/studio/asks.ts +2 −1
@@ -66,6 +66,7 @@ import { btn, row } from "../ui.ts";
6666 import { lookUrl } from "../../core/looks.ts";
6767 import { thumbLook } from "../pages/looks.ts";
6868 import { changeHref } from "./nouns.ts";
69 import { NO_LONGER_ASKS } from "../../actions/asks_words.ts";
6970 import { testsClause } from "./words.ts";
7071
7172 /** Repos narrowed to those with an app record, with the records — the one
@@ -1506,7 +1507,7 @@ export function askTargetsFor(
15061507 const ask = asks.get(item);
15071508 const mine = assigned.get(item);
15081509 if (mine !== undefined) issueIds.push(mine.id);
1509 else if (ask === undefined) refused.push({ item, reason: "it no longer asks you" });
1510 else if (ask === undefined) refused.push({ item, reason: NO_LONGER_ASKS });
15101511 else if (ask.kind === "paused")
15111512 refused.push({ item, reason: "a ceiling is raised in the app's settings" });
15121513 else if (ask.kind === "proposal")
test/asks-op-words.test.ts added +39
@@ -0,0 +1,39 @@
1 /**
2 * An ask that moved on, an unknown bulk operation and a milestone not here
3 * are said from src/actions/asks_words.ts (#5654): the files that answer
4 * with them import the sentence and never type a copy of it.
5 */
6
7 import { expect, test } from "bun:test";
8 import { readFileSync } from "node:fs";
9 import { join } from "node:path";
10 import {
11 NO_LONGER_ASKS,
12 NO_SUCH_MILESTONE,
13 NO_SUCH_OPERATION,
14 } from "../src/actions/asks_words.ts";
15
16 const root = join(import.meta.dir, "..");
17 const read = (path: string) => readFileSync(join(root, path), "utf8");
18
19 const SAID_BY: [string, string, string[]][] = [
20 ["NO_LONGER_ASKS", NO_LONGER_ASKS, ["src/actions/asks_decide.ts", "src/web/studio/asks.ts"]],
21 ["NO_SUCH_OPERATION", NO_SUCH_OPERATION, ["src/actions/asks_bulk.ts", "src/actions/fleet.ts"]],
22 ["NO_SUCH_MILESTONE", NO_SUCH_MILESTONE, ["src/actions/limb_replan.ts", "src/actions/limbs.ts"]],
23 ];
24
25 test("the sentences read as they always have", () => {
26 expect(NO_LONGER_ASKS).toBe("it no longer asks you");
27 expect(NO_SUCH_OPERATION).toBe("no such operation here");
28 expect(NO_SUCH_MILESTONE).toBe("no such milestone here");
29 });
30
31 for (const [name, sentence, files] of SAID_BY) {
32 for (const file of files) {
33 test(`${file} imports ${name} and types no copy of it`, () => {
34 const text = read(file);
35 expect(text).not.toContain(sentence);

This greps source files for the sentence. Add a case that calls getFleetOp with an unknown id and checks the error message equals NO_SUCH_OPERATION, so the test follows behaviour rather than file text.

36 expect(text).toMatch(new RegExp(`import \\{[^}]*\\b${name}\\b[^}]*\\} from "[./]+(actions/)?asks_words\\.ts"`));
37 });
38 }
39 }

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs