github-issue-tower-defence-management 1.146.10 → 1.146.11

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>TDPM Console</title>
7
- <script type="module" crossorigin src="/assets/index-CPY6wV8C.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-D868xHqH.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-CKX2ny-z.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.146.10",
3
+ "version": "1.146.11",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -229,6 +229,92 @@ describe('consoleOperationApi', () => {
229
229
  expectRecordedAcrossTabs('PVTI_c');
230
230
  });
231
231
 
232
+ it('marks a pull request unnecessary by closing it, labelling the item chore and moving it to Awaiting workspace', async () => {
233
+ issueRepository.getIssueByUrl.mockResolvedValue({
234
+ ...issue,
235
+ url: 'https://github.com/o/r/issues/7',
236
+ org: 'o',
237
+ repo: 'r',
238
+ number: 7,
239
+ labels: ['bug'],
240
+ });
241
+ const response = await handleReview(context, {
242
+ pjcode: 'acme',
243
+ action: 'unnecessary',
244
+ prUrl: 'https://github.com/o/r/pull/1',
245
+ issueUrl: 'https://github.com/o/r/issues/7',
246
+ projectItemId: 'PVTI_unnecessary',
247
+ commentBody: 'This pull request is unnecessary.',
248
+ });
249
+ expect(response.statusCode).toBe(200);
250
+ expect(issueRepository.closePullRequest).toHaveBeenCalledWith(
251
+ 'https://github.com/o/r/pull/1',
252
+ );
253
+ expect(issueRepository.createCommentByUrl).toHaveBeenCalledWith(
254
+ 'https://github.com/o/r/pull/1',
255
+ 'This pull request is unnecessary.',
256
+ );
257
+ expect(issueRepository.updateLabels).toHaveBeenCalledWith(
258
+ expect.objectContaining({ url: 'https://github.com/o/r/issues/7' }),
259
+ ['bug', 'chore'],
260
+ );
261
+ expect(issueRepository.updateStatus).toHaveBeenCalledWith(
262
+ project,
263
+ expect.objectContaining({ itemId: 'PVTI_unnecessary' }),
264
+ 'status_aw',
265
+ );
266
+ expectRecordedAcrossTabs('PVTI_unnecessary');
267
+ });
268
+
269
+ it('keeps the chore label once when the unnecessary item already carries it', async () => {
270
+ issueRepository.getIssueByUrl.mockResolvedValue({
271
+ ...issue,
272
+ url: 'https://github.com/o/r/issues/8',
273
+ org: 'o',
274
+ repo: 'r',
275
+ number: 8,
276
+ labels: ['chore'],
277
+ });
278
+ const response = await handleReview(context, {
279
+ pjcode: 'acme',
280
+ action: 'unnecessary',
281
+ prUrl: 'https://github.com/o/r/pull/2',
282
+ issueUrl: 'https://github.com/o/r/issues/8',
283
+ projectItemId: 'PVTI_unnecessary_dup',
284
+ });
285
+ expect(response.statusCode).toBe(200);
286
+ expect(issueRepository.updateLabels).not.toHaveBeenCalled();
287
+ expect(issueRepository.updateStatus).toHaveBeenCalledWith(
288
+ project,
289
+ expect.objectContaining({ itemId: 'PVTI_unnecessary_dup' }),
290
+ 'status_aw',
291
+ );
292
+ });
293
+
294
+ it('rejects an unnecessary action without an issueUrl', async () => {
295
+ const response = await handleReview(context, {
296
+ pjcode: 'acme',
297
+ action: 'unnecessary',
298
+ prUrl: 'https://github.com/o/r/pull/1',
299
+ projectItemId: 'PVTI_unnecessary_missing',
300
+ });
301
+ expect(response.statusCode).toBe(400);
302
+ expect(issueRepository.closePullRequest).not.toHaveBeenCalled();
303
+ });
304
+
305
+ it('leaves labels and status untouched for the plain close action', async () => {
306
+ const response = await handleReview(context, {
307
+ pjcode: 'acme',
308
+ action: 'close',
309
+ prUrl: 'https://github.com/o/r/pull/1',
310
+ projectItemId: 'PVTI_plain_close',
311
+ commentBody: 'closing',
312
+ });
313
+ expect(response.statusCode).toBe(200);
314
+ expect(issueRepository.updateLabels).not.toHaveBeenCalled();
315
+ expect(issueRepository.updateStatus).not.toHaveBeenCalled();
316
+ });
317
+
232
318
  it('rejects an unknown review action', async () => {
233
319
  const response = await handleReview(context, {
234
320
  pjcode: 'acme',
@@ -10,6 +10,7 @@ import { findConsoleItemUrl } from './consoleItemUrlLookup';
10
10
 
11
11
  export const AWAITING_WORKSPACE_STATUS_NAME = 'awaiting workspace';
12
12
  export const IN_TMUX_BY_HUMAN_STATUS_NAME = 'in tmux by human';
13
+ export const CHORE_LABEL_NAME = 'chore';
13
14
 
14
15
  export type ConsoleProjectBinding = {
15
16
  pjcode: string;
@@ -192,6 +193,24 @@ const updateStatusByName = async (
192
193
  return null;
193
194
  };
194
195
 
196
+ const addChoreLabel = async (
197
+ issueRepository: IssueRepository,
198
+ issueUrl: string,
199
+ ): Promise<ConsoleOperationResponse | null> => {
200
+ const issue = await issueRepository.getIssueByUrl(issueUrl);
201
+ if (issue === null) {
202
+ return badGateway(`issue "${issueUrl}" could not be loaded`);
203
+ }
204
+ if (issue.labels.includes(CHORE_LABEL_NAME)) {
205
+ return null;
206
+ }
207
+ await issueRepository.updateLabels(issue, [
208
+ ...issue.labels,
209
+ CHORE_LABEL_NAME,
210
+ ]);
211
+ return null;
212
+ };
213
+
195
214
  export const handleReview = async (
196
215
  context: ConsoleOperationContext,
197
216
  body: Record<string, unknown>,
@@ -230,6 +249,38 @@ export const handleReview = async (
230
249
  }
231
250
  const { project, pjcode } = binding;
232
251
 
252
+ if (action === 'unnecessary') {
253
+ const issueUrl = body.issueUrl;
254
+ if (!isNonEmptyString(issueUrl)) {
255
+ return badRequest('issueUrl is required for unnecessary');
256
+ }
257
+ await context.resolveIssueRepository(prUrl).closePullRequest(prUrl);
258
+ if (isNonEmptyString(body.commentBody)) {
259
+ await context
260
+ .resolveIssueRepository(prUrl)
261
+ .createCommentByUrl(prUrl, body.commentBody);
262
+ }
263
+ const labelFailure = await addChoreLabel(
264
+ context.resolveIssueRepository(issueUrl),
265
+ issueUrl,
266
+ );
267
+ if (labelFailure !== null) {
268
+ return labelFailure;
269
+ }
270
+ const failure = await updateStatusByName(
271
+ context.resolveIssueRepository(issueUrl),
272
+ project,
273
+ issueUrl,
274
+ projectItemId,
275
+ AWAITING_WORKSPACE_STATUS_NAME,
276
+ );
277
+ if (failure !== null) {
278
+ return failure;
279
+ }
280
+ recordDone(context, pjcode, projectItemId);
281
+ return ok();
282
+ }
283
+
233
284
  if (action === 'approve') {
234
285
  await context.resolveIssueRepository(prUrl).approvePullRequest(prUrl);
235
286
  const failure = await updateStatusByName(
@@ -72,7 +72,7 @@ describe('useConsoleOperations', () => {
72
72
  });
73
73
  });
74
74
 
75
- it('posts an unnecessary review as a close with the unnecessary comment body', async () => {
75
+ it('posts an unnecessary review carrying the item url so the server can label it chore', async () => {
76
76
  const fetchMock = captureFetch();
77
77
  const { result } = setup();
78
78
  await act(async () => {
@@ -83,7 +83,8 @@ describe('useConsoleOperations', () => {
83
83
  );
84
84
  });
85
85
  expect(lastBody(fetchMock)).toMatchObject({
86
- action: 'close',
86
+ action: 'unnecessary',
87
+ issueUrl: prItem.url,
87
88
  commentBody: 'This pull request is unnecessary.',
88
89
  });
89
90
  });
@@ -114,9 +114,10 @@ const reviewRequest = (
114
114
  }
115
115
  return {
116
116
  pjcode,
117
- action: 'close',
117
+ action: 'unnecessary',
118
118
  prUrl,
119
119
  projectItemId: item.projectItemId,
120
+ issueUrl: item.url,
120
121
  commentBody: UNNECESSARY_COMMENT_BODY,
121
122
  };
122
123
  };
@@ -23,6 +23,7 @@ export type ConsoleReviewRequest = {
23
23
  action: string;
24
24
  prUrl: string;
25
25
  projectItemId: string;
26
+ issueUrl?: string;
26
27
  commentBody?: string;
27
28
  changedFilePath?: string;
28
29
  line?: number;