@kud/jira-ink 0.7.0 → 0.8.0
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.
- package/dist/index.d.ts +9 -1
- package/dist/index.js +246 -210
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ type IssueDetail = {
|
|
|
19
19
|
assignee: string;
|
|
20
20
|
reporter: string;
|
|
21
21
|
labels: string[];
|
|
22
|
+
/** Jira's priority name, verbatim; absent when the field is off. */
|
|
23
|
+
priority?: string;
|
|
22
24
|
parent?: {
|
|
23
25
|
key: string;
|
|
24
26
|
summary: string;
|
|
@@ -79,6 +81,12 @@ type IssueBodyProps = {
|
|
|
79
81
|
/** Instance URL, as `loadConfig` resolves it; only used to build the browser link. */
|
|
80
82
|
baseUrl: string;
|
|
81
83
|
issueKey: string;
|
|
84
|
+
/**
|
|
85
|
+
* The summary as the host already knows it — from the list row that was
|
|
86
|
+
* opened — so the frame carries the title from the first frame rather than
|
|
87
|
+
* only once the fetch answers. The fetched summary wins once it is here.
|
|
88
|
+
*/
|
|
89
|
+
summary?: string;
|
|
82
90
|
onExit: () => void;
|
|
83
91
|
/** Override the terminal-derived size when the host draws inside a frame. */
|
|
84
92
|
width?: number;
|
|
@@ -97,7 +105,7 @@ type IssueBodyProps = {
|
|
|
97
105
|
* `IssueDetailView` for jira-cli's own TUI, which owns the prompts they need;
|
|
98
106
|
* here they are simply not wired, so the hint row does not offer them.
|
|
99
107
|
*/
|
|
100
|
-
declare const IssueBody: ({ client, baseUrl, issueKey, onExit, width, height, frame, theme, }: IssueBodyProps) => string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null | undefined;
|
|
108
|
+
declare const IssueBody: ({ client, baseUrl, issueKey, summary, onExit, width, height, frame, theme, }: IssueBodyProps) => string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null | undefined;
|
|
101
109
|
|
|
102
110
|
/** Jira's own three, plus `unknown` for a status that arrives without one. */
|
|
103
111
|
type StatusCategory = "new" | "indeterminate" | "done" | "unknown";
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,191 @@
|
|
|
1
1
|
import { MarkdownViewport } from '@kud/ink-markdown';
|
|
2
|
-
import { colors,
|
|
2
|
+
import { colors, KeyValue, Badge, FooterHints, StatusMessage, useTabs, useListCursor, pillWidth, TextInput, Tabs, SelectableRow, Pill, LoadingScreen } from '@kud/ink-ui';
|
|
3
3
|
import { useInput, Box, Text, useStdout } from 'ink';
|
|
4
4
|
import { useState, useEffect, useMemo } from 'react';
|
|
5
|
+
import { isContainerType, locateAttachments, adfToMarkdown, looksLikeJql } from '@kud/jira';
|
|
5
6
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
7
|
import { exec } from 'child_process';
|
|
7
|
-
import { locateAttachments, adfToMarkdown, isContainerType, looksLikeJql } from '@kud/jira';
|
|
8
8
|
|
|
9
9
|
// src/components/issue-detail-view.tsx
|
|
10
|
+
var CATEGORIES = ["new", "indeterminate", "done"];
|
|
11
|
+
var categoryOf = (key) => CATEGORIES.find((c) => c === key) ?? "unknown";
|
|
12
|
+
var toBoardRow = (issue) => ({
|
|
13
|
+
key: issue.key,
|
|
14
|
+
summary: issue.fields.summary ?? "",
|
|
15
|
+
status: issue.fields.status?.name ?? "\u2014",
|
|
16
|
+
statusId: issue.fields.status?.id ?? "",
|
|
17
|
+
category: categoryOf(issue.fields.status?.statusCategory?.key),
|
|
18
|
+
type: issue.fields.issuetype?.name ?? "",
|
|
19
|
+
container: isContainerType(issue.fields.issuetype),
|
|
20
|
+
priority: issue.fields.priority?.name ?? null,
|
|
21
|
+
...issue.fields.parent ? {
|
|
22
|
+
parent: {
|
|
23
|
+
key: issue.fields.parent.key,
|
|
24
|
+
summary: issue.fields.parent.fields?.summary ?? ""
|
|
25
|
+
}
|
|
26
|
+
} : {},
|
|
27
|
+
assignee: issue.fields.assignee?.displayName ?? "unassigned",
|
|
28
|
+
updated: issue.fields.updated ?? ""
|
|
29
|
+
});
|
|
30
|
+
var OFF_BOARD = "__off_board__";
|
|
31
|
+
var CATEGORY_TABS = [
|
|
32
|
+
{ value: "todo", label: "To do", category: "new" },
|
|
33
|
+
{ value: "doing", label: "In progress", category: "indeterminate" },
|
|
34
|
+
{ value: "done", label: "Done", category: "done" }
|
|
35
|
+
];
|
|
36
|
+
var withOffBoard = (tabs) => [
|
|
37
|
+
...tabs,
|
|
38
|
+
{ value: OFF_BOARD, label: "Off board", offBoard: true }
|
|
39
|
+
];
|
|
40
|
+
var tabsFromConfig = (configured) => ({
|
|
41
|
+
source: "config",
|
|
42
|
+
tabs: withOffBoard(
|
|
43
|
+
configured.map((t, i) => ({
|
|
44
|
+
value: `config:${i}`,
|
|
45
|
+
label: t.label,
|
|
46
|
+
statuses: new Set(t.statuses.map((s) => s.toLowerCase()))
|
|
47
|
+
}))
|
|
48
|
+
)
|
|
49
|
+
});
|
|
50
|
+
var tabsFromBoard = (configuration) => {
|
|
51
|
+
const columns = configuration.columnConfig?.columns ?? [];
|
|
52
|
+
if (columns.length === 0) return null;
|
|
53
|
+
return {
|
|
54
|
+
source: "board",
|
|
55
|
+
tabs: withOffBoard(
|
|
56
|
+
columns.map((c, i) => ({
|
|
57
|
+
value: `column:${i}`,
|
|
58
|
+
label: c.name,
|
|
59
|
+
statuses: new Set(c.statuses.map((s) => s.id))
|
|
60
|
+
}))
|
|
61
|
+
)
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
var tabsFromCategories = () => ({
|
|
65
|
+
source: "categories",
|
|
66
|
+
tabs: CATEGORY_TABS
|
|
67
|
+
});
|
|
68
|
+
var tabOf = (row, { tabs }) => {
|
|
69
|
+
const claimed = tabs.find(
|
|
70
|
+
(t) => t.statuses?.has(row.statusId) || t.statuses?.has(row.status.toLowerCase())
|
|
71
|
+
);
|
|
72
|
+
if (claimed) return claimed.value;
|
|
73
|
+
const byCategory = tabs.find((t) => t.category && t.category === row.category);
|
|
74
|
+
if (byCategory) return byCategory.value;
|
|
75
|
+
return tabs.find((t) => t.offBoard)?.value ?? "doing";
|
|
76
|
+
};
|
|
77
|
+
var countsFor = (rows, tabs) => {
|
|
78
|
+
const counts = Object.fromEntries(
|
|
79
|
+
tabs.tabs.map((t) => [t.value, 0])
|
|
80
|
+
);
|
|
81
|
+
for (const row of rows)
|
|
82
|
+
counts[tabOf(row, tabs)] = (counts[tabOf(row, tabs)] ?? 0) + 1;
|
|
83
|
+
return counts;
|
|
84
|
+
};
|
|
85
|
+
var visibleTabs = (tabs, counts) => tabs.tabs.filter((t) => !t.offBoard || (counts[t.value] ?? 0) > 0);
|
|
86
|
+
var blocksFor = (rows) => {
|
|
87
|
+
const hasStructure = rows.some((r) => r.parent || r.container);
|
|
88
|
+
if (!hasStructure)
|
|
89
|
+
return rows.map((row) => ({ kind: "issue", row, depth: 0 }));
|
|
90
|
+
const heads = new Map(rows.filter((r) => r.container).map((r) => [r.key, r]));
|
|
91
|
+
const children = /* @__PURE__ */ new Map();
|
|
92
|
+
const orphans = [];
|
|
93
|
+
for (const row of rows) {
|
|
94
|
+
if (row.container) continue;
|
|
95
|
+
const key = row.parent?.key;
|
|
96
|
+
if (!key) {
|
|
97
|
+
orphans.push(row);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
children.set(key, [...children.get(key) ?? [], row]);
|
|
101
|
+
}
|
|
102
|
+
const groups = [];
|
|
103
|
+
for (const [key, head] of heads)
|
|
104
|
+
groups.push([
|
|
105
|
+
{ kind: "issue", row: head, depth: 0 },
|
|
106
|
+
...(children.get(key) ?? []).map((row) => ({
|
|
107
|
+
kind: "issue",
|
|
108
|
+
row,
|
|
109
|
+
depth: 1
|
|
110
|
+
}))
|
|
111
|
+
]);
|
|
112
|
+
for (const [key, kids] of children) {
|
|
113
|
+
if (heads.has(key)) continue;
|
|
114
|
+
groups.push([
|
|
115
|
+
{ kind: "fence", key, summary: kids[0].parent?.summary ?? "" },
|
|
116
|
+
...kids.map((row) => ({ kind: "issue", row, depth: 1 }))
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
if (orphans.length)
|
|
120
|
+
groups.push([
|
|
121
|
+
...groups.length ? [{ kind: "fence", key: null, summary: "No epic" }] : [],
|
|
122
|
+
...orphans.map((row) => ({ kind: "issue", row, depth: 0 }))
|
|
123
|
+
]);
|
|
124
|
+
return groups.flatMap(
|
|
125
|
+
(group, i) => i === 0 ? group : [{ kind: "gap" }, ...group]
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
var blockIndexOfIssue = (blocks, issueIndex) => {
|
|
129
|
+
let seen = -1;
|
|
130
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
131
|
+
if (blocks[i].kind === "issue" && ++seen === issueIndex) return i;
|
|
132
|
+
}
|
|
133
|
+
return 0;
|
|
134
|
+
};
|
|
135
|
+
var priorityGlyph = (name) => {
|
|
136
|
+
const n = (name ?? "").trim().toLowerCase();
|
|
137
|
+
if (/^(highest|blocker|critical|p[01])$/.test(n)) return "\u21C8";
|
|
138
|
+
if (/^(high|urgent|major|p2)$/.test(n)) return "\u2191";
|
|
139
|
+
if (/^(medium|normal|p3)$/.test(n)) return "=";
|
|
140
|
+
if (/^(low|minor|p4)$/.test(n)) return "\u2193";
|
|
141
|
+
if (/^(lowest|trivial|p5)$/.test(n)) return "\u21CA";
|
|
142
|
+
return " ";
|
|
143
|
+
};
|
|
144
|
+
var UNITS = [
|
|
145
|
+
[60, "m"],
|
|
146
|
+
[60, "h"],
|
|
147
|
+
[24, "d"],
|
|
148
|
+
[7, "w"]
|
|
149
|
+
];
|
|
150
|
+
var relativeAge = (iso, now = Date.now()) => {
|
|
151
|
+
const then = Date.parse(iso);
|
|
152
|
+
if (Number.isNaN(then)) return "";
|
|
153
|
+
let value = Math.max(0, Math.round((now - then) / 1e3));
|
|
154
|
+
let unit = "s";
|
|
155
|
+
for (const [size, next] of UNITS) {
|
|
156
|
+
if (value < size) break;
|
|
157
|
+
value = Math.round(value / size);
|
|
158
|
+
unit = next;
|
|
159
|
+
}
|
|
160
|
+
return `${value}${unit}`;
|
|
161
|
+
};
|
|
162
|
+
var pillVariantFor = (row) => {
|
|
163
|
+
if (row.container) return "group";
|
|
164
|
+
const t = row.type.toLowerCase();
|
|
165
|
+
if (t === "bug") return "error";
|
|
166
|
+
if (t === "story") return "info";
|
|
167
|
+
return "muted";
|
|
168
|
+
};
|
|
169
|
+
var boardOf = async (client, jql, { tabs, board, limit = 200 } = {}) => {
|
|
170
|
+
const [issues, derived] = await Promise.all([
|
|
171
|
+
client.searchIssues(jql, { limit }),
|
|
172
|
+
tabs?.length ? Promise.resolve(tabsFromConfig(tabs)) : board !== void 0 ? client.getBoardConfiguration(board).then(tabsFromBoard) : Promise.resolve(null)
|
|
173
|
+
]);
|
|
174
|
+
return { rows: issues.map(toBoardRow), tabs: derived ?? tabsFromCategories() };
|
|
175
|
+
};
|
|
176
|
+
var COLOUR = {
|
|
177
|
+
"\u21C8": colors.warning,
|
|
178
|
+
"\u2191": colors.warning,
|
|
179
|
+
"=": colors.muted,
|
|
180
|
+
"\u2193": colors.info,
|
|
181
|
+
"\u21CA": colors.info,
|
|
182
|
+
" ": void 0
|
|
183
|
+
};
|
|
184
|
+
var priorityMarker = (name) => {
|
|
185
|
+
const marker = priorityGlyph(name);
|
|
186
|
+
const color = COLOUR[marker];
|
|
187
|
+
return color ? { marker, color } : { marker };
|
|
188
|
+
};
|
|
10
189
|
var truncate = (s, max) => s.length <= max ? s : `${s.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
11
190
|
var originOf = (a) => a.origins.map(
|
|
12
191
|
(o) => o.kind === "comment" ? `comment by ${o.author ?? "unknown"}` : o.kind === "description" ? "description" : "issue"
|
|
@@ -20,14 +199,52 @@ var humanSize = (bytes) => {
|
|
|
20
199
|
const value = bytes / 1024 ** e;
|
|
21
200
|
return `${e === 0 ? value : value.toFixed(1)}${units[e]}`;
|
|
22
201
|
};
|
|
23
|
-
var
|
|
202
|
+
var ago = (iso) => {
|
|
203
|
+
const rel = relativeAge(iso);
|
|
204
|
+
return rel ? `${rel} ago` : iso;
|
|
205
|
+
};
|
|
206
|
+
var documentOf = (issue) => {
|
|
207
|
+
const parts = [issue.description || "_No description._"];
|
|
208
|
+
if (issue.comments.length > 0)
|
|
209
|
+
parts.push(
|
|
210
|
+
`## Comments (${issue.comments.length})`,
|
|
211
|
+
...issue.comments.map(
|
|
212
|
+
(c) => `**${c.author}** \xB7 ${ago(c.created)}
|
|
24
213
|
|
|
25
|
-
${c.body}`
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
214
|
+
${c.body}`
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
if (issue.attachments.length > 0)
|
|
218
|
+
parts.push(
|
|
219
|
+
`## Attachments (${issue.attachments.length})`,
|
|
220
|
+
issue.attachments.map(
|
|
221
|
+
(a) => `- \`${a.filename}\` \u2014 ${a.mimeType}, ${humanSize(a.size)} \xB7 from ${originOf(a)}
|
|
29
222
|
\`jira attachment read ${issue.key} ${a.filename}\``
|
|
30
|
-
).join("\n")
|
|
223
|
+
).join("\n")
|
|
224
|
+
);
|
|
225
|
+
return parts.join("\n\n");
|
|
226
|
+
};
|
|
227
|
+
var FactsLine = ({ issue }) => {
|
|
228
|
+
const priority = priorityMarker(issue.priority ?? null);
|
|
229
|
+
const lead = priority.marker !== " " && priority.marker !== "=" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
230
|
+
/* @__PURE__ */ jsx(Text, { color: priority.color, children: priority.marker }),
|
|
231
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
232
|
+
" ",
|
|
233
|
+
issue.priority
|
|
234
|
+
] })
|
|
235
|
+
] }) : null;
|
|
236
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
237
|
+
const facts = [
|
|
238
|
+
`by ${issue.reporter}`,
|
|
239
|
+
...issue.labels.length > 0 ? [`labels ${issue.labels.join(", ")}`] : [],
|
|
240
|
+
plural(issue.comments.length, "comment"),
|
|
241
|
+
...issue.attachments.length > 0 ? [plural(issue.attachments.length, "attachment")] : []
|
|
242
|
+
];
|
|
243
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
244
|
+
lead,
|
|
245
|
+
/* @__PURE__ */ jsx(Text, { color: colors.muted, children: (lead ? " \xB7 " : "") + facts.join(" \xB7 ") })
|
|
246
|
+
] });
|
|
247
|
+
};
|
|
31
248
|
var IssueDetailView = ({
|
|
32
249
|
issue,
|
|
33
250
|
height,
|
|
@@ -40,16 +257,6 @@ var IssueDetailView = ({
|
|
|
40
257
|
frame,
|
|
41
258
|
theme
|
|
42
259
|
}) => {
|
|
43
|
-
const tabs = [
|
|
44
|
-
{ value: "description", label: "Description" },
|
|
45
|
-
{ value: "comments", label: "Comments", count: issue.comments.length },
|
|
46
|
-
{
|
|
47
|
-
value: "attachments",
|
|
48
|
-
label: "Attachments",
|
|
49
|
-
count: issue.attachments.length
|
|
50
|
-
}
|
|
51
|
-
];
|
|
52
|
-
const { active } = useTabs(tabs, { initial: "description" });
|
|
53
260
|
const [scroll, setScroll] = useState(0);
|
|
54
261
|
useInput((input, key) => {
|
|
55
262
|
if (input === "t" && onTransition) return onTransition();
|
|
@@ -59,12 +266,11 @@ var IssueDetailView = ({
|
|
|
59
266
|
if (key.downArrow || input === "j") setScroll((s) => s + 1);
|
|
60
267
|
if (key.upArrow || input === "k") setScroll((s) => Math.max(0, s - 1));
|
|
61
268
|
});
|
|
62
|
-
const source =
|
|
63
|
-
const keyValueRows =
|
|
64
|
-
const chromeLines = keyValueRows + 1 +
|
|
269
|
+
const source = documentOf(issue);
|
|
270
|
+
const keyValueRows = 2 + (issue.parent ? 1 : 0) + (frame ? 0 : 1);
|
|
271
|
+
const chromeLines = keyValueRows + 1 + 1 + (frame ? 0 : 4);
|
|
65
272
|
const bodyHeight = Math.max(3, height - chromeLines);
|
|
66
273
|
const hints = [
|
|
67
|
-
["\u2190\u2192", "section"],
|
|
68
274
|
["\u2191\u2193", "scroll"],
|
|
69
275
|
...onTransition ? [["t", "move"]] : [],
|
|
70
276
|
...onComment ? [["c", "comment"]] : [],
|
|
@@ -74,20 +280,27 @@ var IssueDetailView = ({
|
|
|
74
280
|
];
|
|
75
281
|
const body = /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
76
282
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
283
|
+
/* @__PURE__ */ jsx(KeyValue, { label: "status", value: /* @__PURE__ */ jsx(Badge, { children: issue.status }) }),
|
|
284
|
+
frame ? null : /* @__PURE__ */ jsx(KeyValue, { label: "type", value: issue.type }),
|
|
285
|
+
/* @__PURE__ */ jsx(
|
|
286
|
+
KeyValue,
|
|
287
|
+
{
|
|
288
|
+
label: "assignee",
|
|
289
|
+
value: issue.assignee === "unassigned" ? /* @__PURE__ */ jsx(Text, { color: colors.muted, children: "unassigned" }) : issue.assignee
|
|
290
|
+
}
|
|
291
|
+
),
|
|
77
292
|
issue.parent && /* @__PURE__ */ jsx(
|
|
78
293
|
KeyValue,
|
|
79
294
|
{
|
|
80
295
|
label: "parent",
|
|
81
|
-
value:
|
|
296
|
+
value: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
297
|
+
/* @__PURE__ */ jsx(Text, { color: colors.secondary, children: issue.parent.key }),
|
|
298
|
+
/* @__PURE__ */ jsx(Text, { children: ` ${truncate(issue.parent.summary, 60)}` })
|
|
299
|
+
] })
|
|
82
300
|
}
|
|
83
301
|
),
|
|
84
|
-
/* @__PURE__ */ jsx(
|
|
85
|
-
frame ? null : /* @__PURE__ */ jsx(KeyValue, { label: "type", value: issue.type }),
|
|
86
|
-
/* @__PURE__ */ jsx(KeyValue, { label: "assignee", value: issue.assignee }),
|
|
87
|
-
/* @__PURE__ */ jsx(KeyValue, { label: "reporter", value: issue.reporter }),
|
|
88
|
-
issue.labels.length > 0 && /* @__PURE__ */ jsx(KeyValue, { label: "labels", value: issue.labels.join(", ") })
|
|
302
|
+
/* @__PURE__ */ jsx(FactsLine, { issue })
|
|
89
303
|
] }),
|
|
90
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Tabs, { items: tabs, active: active ?? "description" }) }),
|
|
91
304
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, height: bodyHeight, overflow: "hidden", children: /* @__PURE__ */ jsx(
|
|
92
305
|
MarkdownViewport,
|
|
93
306
|
{
|
|
@@ -129,13 +342,14 @@ var issueDetailOf = async (client, baseUrl, key) => {
|
|
|
129
342
|
assignee: f.assignee?.displayName ?? "unassigned",
|
|
130
343
|
reporter: f.reporter?.displayName ?? "\u2014",
|
|
131
344
|
labels: f.labels ?? [],
|
|
345
|
+
...f.priority?.name ? { priority: f.priority.name } : {},
|
|
132
346
|
parent: f.parent ? { key: f.parent.key, summary: f.parent.fields?.summary ?? "" } : void 0,
|
|
133
347
|
url: `${baseUrl}/browse/${issue.key}`,
|
|
134
348
|
description: adfToMarkdown(f.description, filenameOf),
|
|
135
349
|
comments: (f.comment?.comments ?? []).map((c) => ({
|
|
136
350
|
id: c.id,
|
|
137
351
|
author: c.author?.displayName ?? "unknown",
|
|
138
|
-
created: c.created
|
|
352
|
+
created: c.created ?? "",
|
|
139
353
|
body: adfToMarkdown(c.body, filenameOf)
|
|
140
354
|
})),
|
|
141
355
|
attachments
|
|
@@ -169,6 +383,7 @@ var IssueBody = ({
|
|
|
169
383
|
client,
|
|
170
384
|
baseUrl,
|
|
171
385
|
issueKey,
|
|
386
|
+
summary,
|
|
172
387
|
onExit,
|
|
173
388
|
width,
|
|
174
389
|
height,
|
|
@@ -192,7 +407,7 @@ var IssueBody = ({
|
|
|
192
407
|
}, [client, baseUrl, issueKey]);
|
|
193
408
|
const framed = (body) => frame ? frame({
|
|
194
409
|
title: issueKey,
|
|
195
|
-
subtitle: "",
|
|
410
|
+
subtitle: summary ?? "",
|
|
196
411
|
hints: [["\u232B", "back"]],
|
|
197
412
|
body
|
|
198
413
|
}) : body;
|
|
@@ -219,185 +434,6 @@ var IssueBody = ({
|
|
|
219
434
|
}
|
|
220
435
|
);
|
|
221
436
|
};
|
|
222
|
-
var CATEGORIES = ["new", "indeterminate", "done"];
|
|
223
|
-
var categoryOf = (key) => CATEGORIES.find((c) => c === key) ?? "unknown";
|
|
224
|
-
var toBoardRow = (issue) => ({
|
|
225
|
-
key: issue.key,
|
|
226
|
-
summary: issue.fields.summary ?? "",
|
|
227
|
-
status: issue.fields.status?.name ?? "\u2014",
|
|
228
|
-
statusId: issue.fields.status?.id ?? "",
|
|
229
|
-
category: categoryOf(issue.fields.status?.statusCategory?.key),
|
|
230
|
-
type: issue.fields.issuetype?.name ?? "",
|
|
231
|
-
container: isContainerType(issue.fields.issuetype),
|
|
232
|
-
priority: issue.fields.priority?.name ?? null,
|
|
233
|
-
...issue.fields.parent ? {
|
|
234
|
-
parent: {
|
|
235
|
-
key: issue.fields.parent.key,
|
|
236
|
-
summary: issue.fields.parent.fields?.summary ?? ""
|
|
237
|
-
}
|
|
238
|
-
} : {},
|
|
239
|
-
assignee: issue.fields.assignee?.displayName ?? "unassigned",
|
|
240
|
-
updated: issue.fields.updated ?? ""
|
|
241
|
-
});
|
|
242
|
-
var OFF_BOARD = "__off_board__";
|
|
243
|
-
var CATEGORY_TABS = [
|
|
244
|
-
{ value: "todo", label: "To do", category: "new" },
|
|
245
|
-
{ value: "doing", label: "In progress", category: "indeterminate" },
|
|
246
|
-
{ value: "done", label: "Done", category: "done" }
|
|
247
|
-
];
|
|
248
|
-
var withOffBoard = (tabs) => [
|
|
249
|
-
...tabs,
|
|
250
|
-
{ value: OFF_BOARD, label: "Off board", offBoard: true }
|
|
251
|
-
];
|
|
252
|
-
var tabsFromConfig = (configured) => ({
|
|
253
|
-
source: "config",
|
|
254
|
-
tabs: withOffBoard(
|
|
255
|
-
configured.map((t, i) => ({
|
|
256
|
-
value: `config:${i}`,
|
|
257
|
-
label: t.label,
|
|
258
|
-
statuses: new Set(t.statuses.map((s) => s.toLowerCase()))
|
|
259
|
-
}))
|
|
260
|
-
)
|
|
261
|
-
});
|
|
262
|
-
var tabsFromBoard = (configuration) => {
|
|
263
|
-
const columns = configuration.columnConfig?.columns ?? [];
|
|
264
|
-
if (columns.length === 0) return null;
|
|
265
|
-
return {
|
|
266
|
-
source: "board",
|
|
267
|
-
tabs: withOffBoard(
|
|
268
|
-
columns.map((c, i) => ({
|
|
269
|
-
value: `column:${i}`,
|
|
270
|
-
label: c.name,
|
|
271
|
-
statuses: new Set(c.statuses.map((s) => s.id))
|
|
272
|
-
}))
|
|
273
|
-
)
|
|
274
|
-
};
|
|
275
|
-
};
|
|
276
|
-
var tabsFromCategories = () => ({
|
|
277
|
-
source: "categories",
|
|
278
|
-
tabs: CATEGORY_TABS
|
|
279
|
-
});
|
|
280
|
-
var tabOf = (row, { tabs }) => {
|
|
281
|
-
const claimed = tabs.find(
|
|
282
|
-
(t) => t.statuses?.has(row.statusId) || t.statuses?.has(row.status.toLowerCase())
|
|
283
|
-
);
|
|
284
|
-
if (claimed) return claimed.value;
|
|
285
|
-
const byCategory = tabs.find((t) => t.category && t.category === row.category);
|
|
286
|
-
if (byCategory) return byCategory.value;
|
|
287
|
-
return tabs.find((t) => t.offBoard)?.value ?? "doing";
|
|
288
|
-
};
|
|
289
|
-
var countsFor = (rows, tabs) => {
|
|
290
|
-
const counts = Object.fromEntries(
|
|
291
|
-
tabs.tabs.map((t) => [t.value, 0])
|
|
292
|
-
);
|
|
293
|
-
for (const row of rows)
|
|
294
|
-
counts[tabOf(row, tabs)] = (counts[tabOf(row, tabs)] ?? 0) + 1;
|
|
295
|
-
return counts;
|
|
296
|
-
};
|
|
297
|
-
var visibleTabs = (tabs, counts) => tabs.tabs.filter((t) => !t.offBoard || (counts[t.value] ?? 0) > 0);
|
|
298
|
-
var blocksFor = (rows) => {
|
|
299
|
-
const hasStructure = rows.some((r) => r.parent || r.container);
|
|
300
|
-
if (!hasStructure)
|
|
301
|
-
return rows.map((row) => ({ kind: "issue", row, depth: 0 }));
|
|
302
|
-
const heads = new Map(rows.filter((r) => r.container).map((r) => [r.key, r]));
|
|
303
|
-
const children = /* @__PURE__ */ new Map();
|
|
304
|
-
const orphans = [];
|
|
305
|
-
for (const row of rows) {
|
|
306
|
-
if (row.container) continue;
|
|
307
|
-
const key = row.parent?.key;
|
|
308
|
-
if (!key) {
|
|
309
|
-
orphans.push(row);
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
children.set(key, [...children.get(key) ?? [], row]);
|
|
313
|
-
}
|
|
314
|
-
const groups = [];
|
|
315
|
-
for (const [key, head] of heads)
|
|
316
|
-
groups.push([
|
|
317
|
-
{ kind: "issue", row: head, depth: 0 },
|
|
318
|
-
...(children.get(key) ?? []).map((row) => ({
|
|
319
|
-
kind: "issue",
|
|
320
|
-
row,
|
|
321
|
-
depth: 1
|
|
322
|
-
}))
|
|
323
|
-
]);
|
|
324
|
-
for (const [key, kids] of children) {
|
|
325
|
-
if (heads.has(key)) continue;
|
|
326
|
-
groups.push([
|
|
327
|
-
{ kind: "fence", key, summary: kids[0].parent?.summary ?? "" },
|
|
328
|
-
...kids.map((row) => ({ kind: "issue", row, depth: 1 }))
|
|
329
|
-
]);
|
|
330
|
-
}
|
|
331
|
-
if (orphans.length)
|
|
332
|
-
groups.push([
|
|
333
|
-
...groups.length ? [{ kind: "fence", key: null, summary: "No epic" }] : [],
|
|
334
|
-
...orphans.map((row) => ({ kind: "issue", row, depth: 0 }))
|
|
335
|
-
]);
|
|
336
|
-
return groups.flatMap(
|
|
337
|
-
(group, i) => i === 0 ? group : [{ kind: "gap" }, ...group]
|
|
338
|
-
);
|
|
339
|
-
};
|
|
340
|
-
var blockIndexOfIssue = (blocks, issueIndex) => {
|
|
341
|
-
let seen = -1;
|
|
342
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
343
|
-
if (blocks[i].kind === "issue" && ++seen === issueIndex) return i;
|
|
344
|
-
}
|
|
345
|
-
return 0;
|
|
346
|
-
};
|
|
347
|
-
var priorityGlyph = (name) => {
|
|
348
|
-
const n = (name ?? "").trim().toLowerCase();
|
|
349
|
-
if (/^(highest|blocker|critical|p[01])$/.test(n)) return "\u21C8";
|
|
350
|
-
if (/^(high|urgent|major|p2)$/.test(n)) return "\u2191";
|
|
351
|
-
if (/^(medium|normal|p3)$/.test(n)) return "=";
|
|
352
|
-
if (/^(low|minor|p4)$/.test(n)) return "\u2193";
|
|
353
|
-
if (/^(lowest|trivial|p5)$/.test(n)) return "\u21CA";
|
|
354
|
-
return " ";
|
|
355
|
-
};
|
|
356
|
-
var UNITS = [
|
|
357
|
-
[60, "m"],
|
|
358
|
-
[60, "h"],
|
|
359
|
-
[24, "d"],
|
|
360
|
-
[7, "w"]
|
|
361
|
-
];
|
|
362
|
-
var relativeAge = (iso, now = Date.now()) => {
|
|
363
|
-
const then = Date.parse(iso);
|
|
364
|
-
if (Number.isNaN(then)) return "";
|
|
365
|
-
let value = Math.max(0, Math.round((now - then) / 1e3));
|
|
366
|
-
let unit = "s";
|
|
367
|
-
for (const [size, next] of UNITS) {
|
|
368
|
-
if (value < size) break;
|
|
369
|
-
value = Math.round(value / size);
|
|
370
|
-
unit = next;
|
|
371
|
-
}
|
|
372
|
-
return `${value}${unit}`;
|
|
373
|
-
};
|
|
374
|
-
var pillVariantFor = (row) => {
|
|
375
|
-
if (row.container) return "group";
|
|
376
|
-
const t = row.type.toLowerCase();
|
|
377
|
-
if (t === "bug") return "error";
|
|
378
|
-
if (t === "story") return "info";
|
|
379
|
-
return "muted";
|
|
380
|
-
};
|
|
381
|
-
var boardOf = async (client, jql, { tabs, board, limit = 200 } = {}) => {
|
|
382
|
-
const [issues, derived] = await Promise.all([
|
|
383
|
-
client.searchIssues(jql, { limit }),
|
|
384
|
-
tabs?.length ? Promise.resolve(tabsFromConfig(tabs)) : board !== void 0 ? client.getBoardConfiguration(board).then(tabsFromBoard) : Promise.resolve(null)
|
|
385
|
-
]);
|
|
386
|
-
return { rows: issues.map(toBoardRow), tabs: derived ?? tabsFromCategories() };
|
|
387
|
-
};
|
|
388
|
-
var COLOUR = {
|
|
389
|
-
"\u21C8": colors.warning,
|
|
390
|
-
"\u2191": colors.warning,
|
|
391
|
-
"=": colors.muted,
|
|
392
|
-
"\u2193": colors.info,
|
|
393
|
-
"\u21CA": colors.info,
|
|
394
|
-
" ": void 0
|
|
395
|
-
};
|
|
396
|
-
var priorityMarker = (name) => {
|
|
397
|
-
const marker = priorityGlyph(name);
|
|
398
|
-
const color = COLOUR[marker];
|
|
399
|
-
return color ? { marker, color } : { marker };
|
|
400
|
-
};
|
|
401
437
|
var CHROME = 6;
|
|
402
438
|
var LEGEND = [
|
|
403
439
|
["\u21C8 \u2191", "priority above the default"],
|
package/package.json
CHANGED