@bli-cockpit/cli 0.2.145 → 0.2.146
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -11,29 +11,32 @@ import { askAgentDoor } from "./agent-door.js";
|
|
|
11
11
|
import { writeLine } from "./cli-io.js";
|
|
12
12
|
const TEAM_DIRECTORY_TIMEOUT_MS = 30_000;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* The `/api/team/members` call by itself. The response is the whole org
|
|
15
|
+
* roster either way — it never actually depended on which issues came back —
|
|
16
|
+
* so a caller with its own read already in flight (BLI-10579: `issue list`'s
|
|
17
|
+
* `/api/work/issues`) can start this one at the same time instead of after.
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
const wanted = new Set(assigneeIds.filter((id) => Boolean(id)));
|
|
21
|
-
const directory = new Map();
|
|
22
|
-
if (wanted.size === 0)
|
|
23
|
-
return directory;
|
|
19
|
+
async function fetchTeamDirectory(door) {
|
|
24
20
|
const answer = await askAgentDoor(door, {
|
|
25
21
|
path: "/api/team/members",
|
|
26
22
|
method: "GET",
|
|
27
23
|
label: "issue assignee directory",
|
|
28
24
|
timeoutMs: TEAM_DIRECTORY_TIMEOUT_MS,
|
|
29
25
|
});
|
|
30
|
-
if (!answer.ok)
|
|
31
|
-
|
|
26
|
+
if (!answer.ok)
|
|
27
|
+
return { ok: false, people: [], reason: answer.reason };
|
|
28
|
+
return { ok: true, people: answer.body.people ?? [] };
|
|
29
|
+
}
|
|
30
|
+
function buildDirectory(door, fetched, wanted) {
|
|
31
|
+
const directory = new Map();
|
|
32
|
+
if (wanted.size === 0)
|
|
33
|
+
return directory;
|
|
34
|
+
if (!fetched.ok) {
|
|
35
|
+
writeLine(door.io.stderr, `[issue cli] assignee directory unread ${JSON.stringify({ reason: fetched.reason, wanted: wanted.size })}`);
|
|
32
36
|
return directory;
|
|
33
37
|
}
|
|
34
|
-
const people = answer.body.people ?? [];
|
|
35
38
|
let resolved = 0;
|
|
36
|
-
for (const person of people) {
|
|
39
|
+
for (const person of fetched.people) {
|
|
37
40
|
const userId = person.account?.userId;
|
|
38
41
|
if (!userId || !wanted.has(userId))
|
|
39
42
|
continue;
|
|
@@ -43,6 +46,36 @@ export async function resolveAssigneeDirectory(door, assigneeIds) {
|
|
|
43
46
|
writeLine(door.io.stderr, `[issue cli] assignee directory resolved ${JSON.stringify({ wanted: wanted.size, resolved })}`);
|
|
44
47
|
return directory;
|
|
45
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* One `/api/team/members` call however many issues are in hand. Best-effort:
|
|
51
|
+
* a directory that cannot be read still lets the issue(s) print their bare
|
|
52
|
+
* `assignee_id`, the pre-BLI-10450 behaviour, rather than failing the whole
|
|
53
|
+
* read over an enrichment nobody asked to gate on. Skips the call entirely
|
|
54
|
+
* when nobody in the batch is assigned.
|
|
55
|
+
*/
|
|
56
|
+
export async function resolveAssigneeDirectory(door, assigneeIds) {
|
|
57
|
+
const wanted = new Set(assigneeIds.filter((id) => Boolean(id)));
|
|
58
|
+
if (wanted.size === 0)
|
|
59
|
+
return new Map();
|
|
60
|
+
return buildDirectory(door, await fetchTeamDirectory(door), wanted);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* BLI-10579: start the directory read now, before the assignee ids that will
|
|
64
|
+
* eventually filter it are even known — its network call never needed them.
|
|
65
|
+
* Pair with `finishAssigneeDirectory` once the caller's own request answers.
|
|
66
|
+
*/
|
|
67
|
+
export function beginAssigneeDirectoryFetch(door) {
|
|
68
|
+
return fetchTeamDirectory(door);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Always awaits the in-flight fetch, even when `wanted` turns out empty —
|
|
72
|
+
* it started eagerly, before that was knowable, so there is no unwaited
|
|
73
|
+
* background call left dangling past the command's exit.
|
|
74
|
+
*/
|
|
75
|
+
export async function finishAssigneeDirectory(door, fetchInFlight, assigneeIds) {
|
|
76
|
+
const wanted = new Set(assigneeIds.filter((id) => Boolean(id)));
|
|
77
|
+
return buildDirectory(door, await fetchInFlight, wanted);
|
|
78
|
+
}
|
|
46
79
|
/** `assignee_name`/`assignee_email` alongside the bare id, for JSON output. */
|
|
47
80
|
export function withAssigneeIdentity(row, directory) {
|
|
48
81
|
const identity = row.assignee_id ? directory.get(row.assignee_id) : undefined;
|
package/dist/commands/issue.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor, } from "./agent-door.js";
|
|
29
29
|
import { writeLine } from "./cli-io.js";
|
|
30
|
-
import { assigneeLabel,
|
|
30
|
+
import { assigneeLabel, beginAssigneeDirectoryFetch, finishAssigneeDirectory, withAssigneeIdentity } from "./issue-directory.js";
|
|
31
31
|
import { showIssue } from "./issue-show.js";
|
|
32
32
|
import { READ_DEADLINE_MS, TAG, resolveProjectId, sizeForEstimate, withSizeLabel, } from "./issue-contracts.js";
|
|
33
33
|
import { commentIssue, createIssue, moveIssue, updateIssue } from "./issue-write.js";
|
|
@@ -105,14 +105,24 @@ async function listIssues(command, door) {
|
|
|
105
105
|
query.set("project_id", resolved.id);
|
|
106
106
|
}
|
|
107
107
|
const suffix = query.toString();
|
|
108
|
+
// BLI-10579: the assignee directory (`/api/team/members`) is the whole org
|
|
109
|
+
// roster regardless of which issues come back — it never actually depended
|
|
110
|
+
// on the issue list — so start it now, alongside the issues request,
|
|
111
|
+
// instead of waiting for that answer first (measured ~580ms of the ~580ms
|
|
112
|
+
// `work list` p50 was exactly this serial second call).
|
|
113
|
+
const directoryFetch = beginAssigneeDirectoryFetch(door);
|
|
108
114
|
const answer = await askAgentDoor(door, {
|
|
109
115
|
path: `/api/work/issues${suffix ? `?${suffix}` : ""}`,
|
|
110
116
|
method: "GET",
|
|
111
117
|
label: "issue list",
|
|
112
118
|
timeoutMs: READ_DEADLINE_MS,
|
|
113
119
|
});
|
|
114
|
-
if (!answer.ok)
|
|
120
|
+
if (!answer.ok) {
|
|
121
|
+
// Not awaited for its own sake — `finishAssigneeDirectory` always awaits
|
|
122
|
+
// the in-flight fetch so it never leaks past this command's exit.
|
|
123
|
+
await finishAssigneeDirectory(door, directoryFetch, []);
|
|
115
124
|
return failAgentDoor(door, TAG, answer.reason, answer.detail);
|
|
125
|
+
}
|
|
116
126
|
const body = answer.body;
|
|
117
127
|
const issues = body.issues ?? [];
|
|
118
128
|
// BLI-10447: the gaps go to stderr in both modes, so `--json` stdout stays one object.
|
|
@@ -121,7 +131,7 @@ async function listIssues(command, door) {
|
|
|
121
131
|
// BLI-10450: one extra call for the whole list, never one per row — the
|
|
122
132
|
// uuid `assignee_id` alone forced a second `cockpit team members` call and
|
|
123
133
|
// a join done by hand to answer "who owns what".
|
|
124
|
-
const directory = await
|
|
134
|
+
const directory = await finishAssigneeDirectory(door, directoryFetch, issues.map((issue) => issue.assignee_id));
|
|
125
135
|
if (door.json) {
|
|
126
136
|
return emitAgentDoor(door, {
|
|
127
137
|
ok: true,
|
|
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
24
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
24
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.146");
|
|
25
25
|
return 0;
|
|
26
26
|
}
|
|
27
27
|
|