patchrelay 0.32.1 → 0.32.2
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/build-info.json +3 -3
- package/dist/cli/watch/IssueListView.js +3 -3
- package/dist/cli/watch/IssueRow.js +170 -79
- package/dist/service.js +35 -0
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -4,15 +4,15 @@ import { Box, Text, useStdout } from "ink";
|
|
|
4
4
|
import { IssueRow } from "./IssueRow.js";
|
|
5
5
|
import { StatusBar } from "./StatusBar.js";
|
|
6
6
|
import { HelpBar } from "./HelpBar.js";
|
|
7
|
-
|
|
8
|
-
const FIXED_COLS = 40;
|
|
7
|
+
const FIXED_COLS = 8;
|
|
9
8
|
const CHROME_ROWS = 4;
|
|
9
|
+
const ISSUE_ROW_HEIGHT = 4;
|
|
10
10
|
export function IssueListView({ issues, allIssues, selectedIndex, connected, lastServerMessageAt, filter, totalCount, frozen, }) {
|
|
11
11
|
const { stdout } = useStdout();
|
|
12
12
|
const cols = stdout?.columns ?? 80;
|
|
13
13
|
const rows = stdout?.rows ?? 24;
|
|
14
14
|
const titleWidth = Math.max(0, cols - FIXED_COLS);
|
|
15
|
-
const maxVisible = Math.max(1, rows - CHROME_ROWS);
|
|
15
|
+
const maxVisible = Math.max(1, Math.floor((rows - CHROME_ROWS) / ISSUE_ROW_HEIGHT));
|
|
16
16
|
// Periodic refresh for elapsed times
|
|
17
17
|
const [, tick] = useReducer((c) => c + 1, 0);
|
|
18
18
|
useEffect(() => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { summarizeIssueStatusNote } from "./issue-status-note.js";
|
|
4
|
+
import { progressBar, relativeTime, truncate } from "./format-utils.js";
|
|
4
5
|
const STATE_COLORS = {
|
|
5
6
|
blocked: "yellow",
|
|
6
7
|
ready: "blueBright",
|
|
@@ -20,16 +21,16 @@ const STATE_SHORT = {
|
|
|
20
21
|
blocked: "blocked",
|
|
21
22
|
ready: "ready",
|
|
22
23
|
delegated: "delegated",
|
|
23
|
-
implementing: "
|
|
24
|
+
implementing: "implementing",
|
|
24
25
|
pr_open: "pr open",
|
|
25
|
-
changes_requested: "review
|
|
26
|
-
repairing_ci: "
|
|
27
|
-
awaiting_queue: "
|
|
28
|
-
repairing_queue: "merge
|
|
26
|
+
changes_requested: "review changes",
|
|
27
|
+
repairing_ci: "repairing checks",
|
|
28
|
+
awaiting_queue: "queued for merge",
|
|
29
|
+
repairing_queue: "repairing merge queue",
|
|
29
30
|
done: "done",
|
|
30
31
|
failed: "failed",
|
|
31
32
|
escalated: "escalated",
|
|
32
|
-
awaiting_input: "
|
|
33
|
+
awaiting_input: "awaiting input",
|
|
33
34
|
};
|
|
34
35
|
const STATUS_SHORT = {
|
|
35
36
|
running: "\u25b8",
|
|
@@ -40,115 +41,205 @@ const STATUS_SHORT = {
|
|
|
40
41
|
function stateColor(state) {
|
|
41
42
|
return STATE_COLORS[state] ?? "white";
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
const TERMINAL_STATES = new Set(["done", "failed", "escalated", "awaiting_input"]);
|
|
45
|
+
function formatStatus(issue) {
|
|
46
|
+
const effectiveState = issue.blockedByCount > 0 && !issue.activeRunType
|
|
47
|
+
? "blocked"
|
|
48
|
+
: issue.readyForExecution && !issue.activeRunType
|
|
49
|
+
? "ready"
|
|
50
|
+
: issue.factoryState;
|
|
51
|
+
const state = STATE_SHORT[effectiveState] ?? effectiveState;
|
|
52
|
+
// Terminal states: just the label, no run symbol
|
|
53
|
+
if (TERMINAL_STATES.has(issue.factoryState))
|
|
54
|
+
return state;
|
|
55
|
+
// Active/in-progress: show run status symbol
|
|
56
|
+
const status = issue.activeRunType ? "running" : issue.latestRunStatus;
|
|
57
|
+
const statusSym = status ? (STATUS_SHORT[status] ?? "") : "";
|
|
58
|
+
if (statusSym)
|
|
59
|
+
return `${state} ${statusSym}`;
|
|
60
|
+
return state;
|
|
57
61
|
}
|
|
58
|
-
function
|
|
62
|
+
function buildStatusChips(issue) {
|
|
63
|
+
const effectiveState = issue.blockedByCount > 0 && !issue.activeRunType
|
|
64
|
+
? "blocked"
|
|
65
|
+
: issue.readyForExecution && !issue.activeRunType
|
|
66
|
+
? "ready"
|
|
67
|
+
: issue.factoryState;
|
|
68
|
+
const chips = [{
|
|
69
|
+
text: `${stateIcon(effectiveState)} ${STATE_SHORT[effectiveState] ?? effectiveState}`,
|
|
70
|
+
color: stateColor(effectiveState),
|
|
71
|
+
}];
|
|
72
|
+
if (issue.prNumber !== undefined) {
|
|
73
|
+
chips.push({ text: `PR #${issue.prNumber}`, color: "cyan" });
|
|
74
|
+
}
|
|
75
|
+
const reviewChip = buildReviewChip(issue.prReviewState);
|
|
76
|
+
if (reviewChip)
|
|
77
|
+
chips.push(reviewChip);
|
|
78
|
+
const checkChip = buildCheckChip(issue.prCheckStatus);
|
|
79
|
+
if (checkChip)
|
|
80
|
+
chips.push(checkChip);
|
|
81
|
+
const checksProgressChip = buildChecksProgressChip(issue);
|
|
82
|
+
if (checksProgressChip)
|
|
83
|
+
chips.push(checksProgressChip);
|
|
84
|
+
const mergeChip = buildMergeChip(issue);
|
|
85
|
+
if (mergeChip)
|
|
86
|
+
chips.push(mergeChip);
|
|
87
|
+
if (issue.blockedByCount > 0) {
|
|
88
|
+
chips.push({
|
|
89
|
+
text: `blocked by ${issue.blockedByKeys.join(", ")}`,
|
|
90
|
+
color: "yellow",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return chips;
|
|
94
|
+
}
|
|
95
|
+
function stateIcon(state) {
|
|
96
|
+
switch (state) {
|
|
97
|
+
case "implementing":
|
|
98
|
+
case "repairing_ci":
|
|
99
|
+
case "repairing_queue":
|
|
100
|
+
return "\u25b8";
|
|
101
|
+
case "awaiting_queue":
|
|
102
|
+
return "\u25a4";
|
|
103
|
+
case "done":
|
|
104
|
+
return "\u2713";
|
|
105
|
+
case "failed":
|
|
106
|
+
case "escalated":
|
|
107
|
+
return "\u2717";
|
|
108
|
+
case "blocked":
|
|
109
|
+
return "!";
|
|
110
|
+
case "ready":
|
|
111
|
+
return "+";
|
|
112
|
+
default:
|
|
113
|
+
return "\u2022";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function buildReviewChip(reviewState) {
|
|
59
117
|
switch (reviewState) {
|
|
60
118
|
case "approved":
|
|
61
|
-
return "
|
|
119
|
+
return { text: "\u2713 review approved", color: "green" };
|
|
62
120
|
case "changes_requested":
|
|
63
|
-
return "
|
|
121
|
+
return { text: "\u2717 changes requested", color: "yellow" };
|
|
64
122
|
case "commented":
|
|
65
|
-
return "
|
|
123
|
+
return { text: "\u2022 review commented", color: "yellow" };
|
|
66
124
|
case "dismissed":
|
|
67
|
-
return "
|
|
125
|
+
return { text: "\u2013 review dismissed", color: "yellow" };
|
|
68
126
|
default:
|
|
69
127
|
return null;
|
|
70
128
|
}
|
|
71
129
|
}
|
|
72
|
-
function
|
|
130
|
+
function buildCheckChip(checkState) {
|
|
73
131
|
switch (checkState) {
|
|
74
132
|
case "passed":
|
|
75
133
|
case "success":
|
|
76
|
-
return "
|
|
134
|
+
return { text: "\u2713 checks passed", color: "green" };
|
|
77
135
|
case "failed":
|
|
78
136
|
case "failure":
|
|
79
|
-
return "
|
|
137
|
+
return { text: "\u2717 checks failed", color: "red" };
|
|
80
138
|
case "pending":
|
|
81
139
|
case "in_progress":
|
|
82
140
|
case "queued":
|
|
83
|
-
return "
|
|
141
|
+
return { text: "\u25cf checks running", color: "yellow" };
|
|
84
142
|
default:
|
|
85
143
|
return null;
|
|
86
144
|
}
|
|
87
145
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
146
|
+
function buildChecksProgressChip(issue) {
|
|
147
|
+
const summary = issue.prChecksSummary;
|
|
148
|
+
if (!summary || summary.total <= 0)
|
|
149
|
+
return null;
|
|
150
|
+
const text = summary.failed > 0
|
|
151
|
+
? `checks ${summary.completed}/${summary.total} failed`
|
|
152
|
+
: summary.pending > 0
|
|
153
|
+
? `checks ${summary.completed}/${summary.total} running`
|
|
154
|
+
: `checks ${summary.completed}/${summary.total} passed`;
|
|
155
|
+
const color = summary.failed > 0 ? "red" : summary.pending > 0 ? "yellow" : "green";
|
|
156
|
+
return { text, color };
|
|
157
|
+
}
|
|
158
|
+
function buildMergeChip(issue) {
|
|
159
|
+
if (issue.prNumber === undefined)
|
|
90
160
|
return null;
|
|
91
161
|
switch (issue.factoryState) {
|
|
92
162
|
case "awaiting_queue":
|
|
93
|
-
return "
|
|
163
|
+
return { text: "\u25a4 queued for merge", color: "cyan" };
|
|
94
164
|
case "repairing_queue":
|
|
95
|
-
return "
|
|
165
|
+
return { text: "! merge queue repair", color: "yellow" };
|
|
96
166
|
case "done":
|
|
97
|
-
return "merged";
|
|
167
|
+
return { text: "\u2713 merged", color: "green" };
|
|
98
168
|
case "pr_open":
|
|
99
|
-
if (issue.prReviewState === "approved" && issue.prCheckStatus === "passed")
|
|
100
|
-
return "ready";
|
|
101
|
-
|
|
169
|
+
if (issue.prReviewState === "approved" && issue.prCheckStatus === "passed") {
|
|
170
|
+
return { text: "\u2713 merge ready", color: "green" };
|
|
171
|
+
}
|
|
172
|
+
return { text: "\u2022 PR open", color: "cyan" };
|
|
102
173
|
default:
|
|
103
174
|
return null;
|
|
104
175
|
}
|
|
105
176
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
177
|
+
function buildPrimaryBlocker(issue) {
|
|
178
|
+
if (issue.blockedByCount > 0) {
|
|
179
|
+
return {
|
|
180
|
+
text: `Waiting on ${issue.blockedByKeys.join(", ")}`,
|
|
181
|
+
color: "yellow",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (issue.prCheckStatus === "failed") {
|
|
185
|
+
const failedCheck = issue.latestFailureCheckName ?? "PR checks";
|
|
186
|
+
return {
|
|
187
|
+
text: `${failedCheck} failed`,
|
|
188
|
+
color: "red",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (issue.prReviewState === "changes_requested") {
|
|
192
|
+
return {
|
|
193
|
+
text: "Review changes requested",
|
|
194
|
+
color: "yellow",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (issue.prNumber !== undefined && !issue.prReviewState && issue.factoryState !== "done") {
|
|
198
|
+
return {
|
|
199
|
+
text: "Waiting for review approval",
|
|
200
|
+
color: "yellow",
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (issue.factoryState === "awaiting_queue") {
|
|
204
|
+
return {
|
|
205
|
+
text: "Waiting for merge queue turn",
|
|
206
|
+
color: "yellow",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
126
210
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
211
|
+
function buildPipelineProgress(issue) {
|
|
212
|
+
switch (issue.factoryState) {
|
|
213
|
+
case "delegated":
|
|
214
|
+
return { current: 1, total: 4, label: "delegated" };
|
|
215
|
+
case "implementing":
|
|
216
|
+
return { current: 1, total: 4, label: "implementing" };
|
|
217
|
+
case "pr_open":
|
|
218
|
+
case "changes_requested":
|
|
219
|
+
case "repairing_ci":
|
|
220
|
+
return { current: 2, total: 4, label: "pr checks" };
|
|
221
|
+
case "awaiting_queue":
|
|
222
|
+
case "repairing_queue":
|
|
223
|
+
return { current: 3, total: 4, label: "merge queue" };
|
|
224
|
+
case "done":
|
|
225
|
+
return { current: 4, total: 4, label: "merged" };
|
|
226
|
+
case "failed":
|
|
227
|
+
case "escalated":
|
|
228
|
+
case "awaiting_input":
|
|
229
|
+
return { current: 4, total: 4, label: "stopped" };
|
|
230
|
+
default:
|
|
231
|
+
return { current: 1, total: 4, label: "queued" };
|
|
232
|
+
}
|
|
144
233
|
}
|
|
145
234
|
export function IssueRow({ issue, selected, titleWidth }) {
|
|
146
235
|
const key = issue.issueKey ?? issue.projectId;
|
|
147
|
-
const status = formatStatus(issue);
|
|
148
|
-
const pr = formatPr(issue);
|
|
149
236
|
const ago = relativeTime(issue.updatedAt);
|
|
150
|
-
const tw = titleWidth ??
|
|
237
|
+
const tw = titleWidth ?? 40;
|
|
151
238
|
const title = issue.title ? truncate(issue.title, tw) : "";
|
|
152
239
|
const detail = selected ? summarizeIssueStatusNote(issue.statusNote) : undefined;
|
|
153
|
-
|
|
240
|
+
const status = formatStatus(issue);
|
|
241
|
+
const chips = buildStatusChips(issue);
|
|
242
|
+
const blocker = buildPrimaryBlocker(issue);
|
|
243
|
+
const pipeline = buildPipelineProgress(issue);
|
|
244
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: detail ? 1 : 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: selected ? "blueBright" : "white", bold: selected, children: selected ? "\u25b8" : " " }), _jsx(Text, { bold: true, children: ` ${key}` }), _jsx(Text, { dimColor: true, children: ` ${ago}` }), _jsx(Text, { dimColor: true, children: ` ${status}` })] }), _jsx(Box, { paddingLeft: 2, flexWrap: "wrap", children: title ? _jsx(Text, { children: title }) : null }), _jsx(Box, { paddingLeft: 2, flexWrap: "wrap", children: chips.map((chip, index) => (_jsx(Box, { marginRight: 1, children: _jsxs(Text, { color: chip.color, children: ["[", chip.text, "]"] }) }, `${key}-chip-${index}`))) }), _jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: progressBar(pipeline.current, pipeline.total, 8) }), _jsx(Text, { dimColor: true, children: pipeline.label }), blocker ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "|" }), _jsx(Text, { color: blocker.color, children: blocker.text })] })) : null] }), detail ? (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: detail }) })) : null] }));
|
|
154
245
|
}
|
package/dist/service.js
CHANGED
|
@@ -35,6 +35,38 @@ function extractStatusNote(summaryJson, reportJson) {
|
|
|
35
35
|
}
|
|
36
36
|
return undefined;
|
|
37
37
|
}
|
|
38
|
+
function parseCiSnapshotSummary(snapshotJson) {
|
|
39
|
+
if (!snapshotJson)
|
|
40
|
+
return undefined;
|
|
41
|
+
try {
|
|
42
|
+
const snapshot = JSON.parse(snapshotJson);
|
|
43
|
+
const checks = Array.isArray(snapshot.checks) ? snapshot.checks : [];
|
|
44
|
+
if (checks.length === 0)
|
|
45
|
+
return undefined;
|
|
46
|
+
let passed = 0;
|
|
47
|
+
let failed = 0;
|
|
48
|
+
let pending = 0;
|
|
49
|
+
for (const check of checks) {
|
|
50
|
+
if (check.status === "success")
|
|
51
|
+
passed++;
|
|
52
|
+
else if (check.status === "failure")
|
|
53
|
+
failed++;
|
|
54
|
+
else
|
|
55
|
+
pending++;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
total: checks.length,
|
|
59
|
+
completed: passed + failed,
|
|
60
|
+
passed,
|
|
61
|
+
failed,
|
|
62
|
+
pending,
|
|
63
|
+
overall: snapshot.gateCheckStatus,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
38
70
|
export class PatchRelayService {
|
|
39
71
|
config;
|
|
40
72
|
db;
|
|
@@ -221,6 +253,7 @@ export class PatchRelayService {
|
|
|
221
253
|
i.current_linear_state, i.factory_state, i.updated_at,
|
|
222
254
|
i.pending_run_type,
|
|
223
255
|
i.pr_number, i.pr_review_state, i.pr_check_status,
|
|
256
|
+
i.last_github_ci_snapshot_json,
|
|
224
257
|
i.last_github_failure_source,
|
|
225
258
|
i.last_github_failure_head_sha,
|
|
226
259
|
i.last_github_failure_check_name,
|
|
@@ -267,6 +300,7 @@ export class PatchRelayService {
|
|
|
267
300
|
.all();
|
|
268
301
|
return rows.map((row) => {
|
|
269
302
|
const failureContext = parseGitHubFailureContext(typeof row.last_github_failure_context_json === "string" ? row.last_github_failure_context_json : undefined);
|
|
303
|
+
const prChecksSummary = parseCiSnapshotSummary(typeof row.last_github_ci_snapshot_json === "string" ? row.last_github_ci_snapshot_json : undefined);
|
|
270
304
|
const statusNote = extractStatusNote(typeof row.latest_run_summary_json === "string" ? row.latest_run_summary_json : undefined, typeof row.latest_run_report_json === "string" ? row.latest_run_report_json : undefined);
|
|
271
305
|
const blockedByKeys = parseStringArray(typeof row.blocked_by_keys_json === "string" ? row.blocked_by_keys_json : undefined);
|
|
272
306
|
const blockedByCount = Number(row.blocked_by_count ?? 0);
|
|
@@ -299,6 +333,7 @@ export class PatchRelayService {
|
|
|
299
333
|
...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
|
|
300
334
|
...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
|
|
301
335
|
...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
|
|
336
|
+
...(prChecksSummary ? { prChecksSummary } : {}),
|
|
302
337
|
...(row.last_github_failure_source !== null ? { latestFailureSource: String(row.last_github_failure_source) } : {}),
|
|
303
338
|
...(row.last_github_failure_head_sha !== null ? { latestFailureHeadSha: String(row.last_github_failure_head_sha) } : {}),
|
|
304
339
|
...(row.last_github_failure_check_name !== null ? { latestFailureCheckName: String(row.last_github_failure_check_name) } : {}),
|