github-issue-tower-defence-management 1.146.17 → 1.146.19

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.
Files changed (28) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/adapter/entry-points/console/consoleOperationApi.js +5 -0
  3. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  4. package/bin/adapter/entry-points/console/ui-dist/assets/index-wljqMpcX.js +82 -0
  5. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  6. package/package.json +1 -1
  7. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +27 -0
  8. package/src/adapter/entry-points/console/consoleOperationApi.ts +5 -0
  9. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +3 -1
  10. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +46 -0
  11. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +6 -1
  12. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +4 -1
  13. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.stories.tsx +29 -0
  14. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.test.tsx +30 -0
  15. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestMergeableChip.tsx +50 -0
  16. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.stories.tsx +2 -15
  17. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.test.tsx +0 -48
  18. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestStatusBadges.tsx +1 -35
  19. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +15 -0
  20. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +2 -0
  21. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +1 -0
  22. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.test.ts +9 -0
  23. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +3 -0
  24. package/src/adapter/entry-points/console/ui-dist/assets/index-wljqMpcX.js +82 -0
  25. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  26. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  27. package/bin/adapter/entry-points/console/ui-dist/assets/index-Cuitqkoc.js +0 -82
  28. package/src/adapter/entry-points/console/ui-dist/assets/index-Cuitqkoc.js +0 -82
@@ -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-Cuitqkoc.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-wljqMpcX.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-CDLgAfIB.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.17",
3
+ "version": "1.146.19",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -266,6 +266,33 @@ describe('consoleOperationApi', () => {
266
266
  expectRecordedAcrossTabs('PVTI_unnecessary');
267
267
  });
268
268
 
269
+ it('tells the linked issue that the unnecessary pull request must not be created again', async () => {
270
+ issueRepository.getIssueByUrl.mockResolvedValue({
271
+ ...issue,
272
+ url: 'https://github.com/o/r/issues/9',
273
+ org: 'o',
274
+ repo: 'r',
275
+ number: 9,
276
+ labels: ['bug'],
277
+ });
278
+ const issueCommentBody =
279
+ 'The pull request for this issue was unnecessary and has been closed: https://github.com/o/r/pull/3\n\nDo not create it again.';
280
+ const response = await handleReview(context, {
281
+ pjcode: 'acme',
282
+ action: 'unnecessary',
283
+ prUrl: 'https://github.com/o/r/pull/3',
284
+ issueUrl: 'https://github.com/o/r/issues/9',
285
+ projectItemId: 'PVTI_unnecessary_issue_comment',
286
+ commentBody: 'This pull request is unnecessary.',
287
+ issueCommentBody,
288
+ });
289
+ expect(response.statusCode).toBe(200);
290
+ expect(issueRepository.createCommentByUrl).toHaveBeenCalledWith(
291
+ 'https://github.com/o/r/issues/9',
292
+ issueCommentBody,
293
+ );
294
+ });
295
+
269
296
  it('keeps the chore label once when the unnecessary item already carries it', async () => {
270
297
  issueRepository.getIssueByUrl.mockResolvedValue({
271
298
  ...issue,
@@ -260,6 +260,11 @@ export const handleReview = async (
260
260
  .resolveIssueRepository(prUrl)
261
261
  .createCommentByUrl(prUrl, body.commentBody);
262
262
  }
263
+ if (isNonEmptyString(body.issueCommentBody)) {
264
+ await context
265
+ .resolveIssueRepository(issueUrl)
266
+ .createCommentByUrl(issueUrl, body.issueCommentBody);
267
+ }
263
268
  const labelFailure = await addChoreLabel(
264
269
  context.resolveIssueRepository(issueUrl),
265
270
  issueUrl,
@@ -50,10 +50,12 @@ test('shows CI and conflict badges in the directly opened PR detail header', asy
50
50
  const title = page.locator('.console-detail-title');
51
51
  await expect(title.getByText('CI failing')).toHaveCount(0);
52
52
 
53
+ await expect(title.getByText('Conflict')).toBeVisible();
54
+
53
55
  const statusRow = page.locator('.console-detail-pr-status-row');
54
56
  await expect(statusRow.getByText('CI failing')).toBeVisible();
55
57
  await expect(statusRow.getByText(/missing: build, test/)).toBeVisible();
56
- await expect(statusRow.getByText('Conflict')).toBeVisible();
58
+ await expect(statusRow.getByText('Conflict')).toHaveCount(0);
57
59
  await expect(statusRow.getByText('Out of date')).toBeVisible();
58
60
 
59
61
  await page.locator('.console-detail').screenshot({
@@ -164,6 +164,52 @@ describe('ConsoleItemDetail', () => {
164
164
  expect(title?.contains(getByText('Awaiting Workspace'))).toBe(true);
165
165
  });
166
166
 
167
+ it('renders the merge conflict state inside the top header', () => {
168
+ const { getByText, container } = render(
169
+ <ConsoleItemDetail
170
+ item={prItem}
171
+ {...baseProps}
172
+ pullRequestStatus={{
173
+ found: true,
174
+ isConflicted: true,
175
+ mergeableStatus: 'CONFLICTING',
176
+ isPassedAllCiJob: true,
177
+ isCiStateSuccess: true,
178
+ isBranchOutOfDate: false,
179
+ missingRequiredCheckNames: [],
180
+ }}
181
+ />,
182
+ );
183
+ const title = container.querySelector('.console-detail-title');
184
+ if (title === null) {
185
+ throw new Error('the top header must render');
186
+ }
187
+ expect(title.contains(getByText('Conflict'))).toBe(true);
188
+ });
189
+
190
+ it('renders the absence of a merge conflict inside the top header', () => {
191
+ const { getByText, container } = render(
192
+ <ConsoleItemDetail
193
+ item={prItem}
194
+ {...baseProps}
195
+ pullRequestStatus={{
196
+ found: true,
197
+ isConflicted: false,
198
+ mergeableStatus: 'MERGEABLE',
199
+ isPassedAllCiJob: true,
200
+ isCiStateSuccess: true,
201
+ isBranchOutOfDate: false,
202
+ missingRequiredCheckNames: [],
203
+ }}
204
+ />,
205
+ );
206
+ const title = container.querySelector('.console-detail-title');
207
+ if (title === null) {
208
+ throw new Error('the top header must render');
209
+ }
210
+ expect(title.contains(getByText('No conflict'))).toBe(true);
211
+ });
212
+
167
213
  it('renders failing CI, missing checks, and conflict badges on their own row below the title', () => {
168
214
  const { getByText, container } = render(
169
215
  <ConsoleItemDetail
@@ -27,6 +27,7 @@ import { ConsoleCopyUrlButton } from './ConsoleCopyUrlButton';
27
27
  import type { ConsoleAddInlineComment } from './ConsoleFileDiff';
28
28
  import { ConsoleItemIcon } from './ConsoleItemIcon';
29
29
  import { ConsolePullRequestDetail } from './ConsolePullRequestDetail';
30
+ import { ConsolePullRequestMergeableChip } from './ConsolePullRequestMergeableChip';
30
31
  import { ConsolePullRequestStatusBadges } from './ConsolePullRequestStatusBadges';
31
32
 
32
33
  export type ConsoleRelatedPullRequestView = {
@@ -159,6 +160,11 @@ export const ConsoleItemDetail = ({
159
160
  {closedStateLabel}
160
161
  </span>
161
162
  )}
163
+ {item.isPr && pullRequestStatus?.found && (
164
+ <ConsolePullRequestMergeableChip
165
+ mergeableStatus={pullRequestStatus.mergeableStatus}
166
+ />
167
+ )}
162
168
  </h2>
163
169
 
164
170
  {stateError !== null && (
@@ -176,7 +182,6 @@ export const ConsoleItemDetail = ({
176
182
  {item.isPr && pullRequestStatus?.found && (
177
183
  <div className="console-detail-pr-status-row">
178
184
  <ConsolePullRequestStatusBadges
179
- mergeableStatus={pullRequestStatus.mergeableStatus}
180
185
  isPassedAllCiJob={pullRequestStatus.isPassedAllCiJob}
181
186
  isCiStateSuccess={pullRequestStatus.isCiStateSuccess}
182
187
  isBranchOutOfDate={pullRequestStatus.isBranchOutOfDate}
@@ -11,6 +11,7 @@ import { ConsoleChangedFileList } from './ConsoleChangedFileList';
11
11
  import { ConsoleCommitList } from './ConsoleCommitList';
12
12
  import { ConsoleCopyUrlButton } from './ConsoleCopyUrlButton';
13
13
  import type { ConsoleAddInlineComment } from './ConsoleFileDiff';
14
+ import { ConsolePullRequestMergeableChip } from './ConsolePullRequestMergeableChip';
14
15
  import { ConsolePullRequestStatusBadges } from './ConsolePullRequestStatusBadges';
15
16
 
16
17
  export type ConsolePullRequestSectionProps = {
@@ -63,8 +64,10 @@ export const ConsolePullRequestDetail = ({
63
64
  {pullRequest.isDraft && (
64
65
  <span className="console-pr-section-state">draft</span>
65
66
  )}
66
- <ConsolePullRequestStatusBadges
67
+ <ConsolePullRequestMergeableChip
67
68
  mergeableStatus={pullRequest.mergeableStatus}
69
+ />
70
+ <ConsolePullRequestStatusBadges
68
71
  isPassedAllCiJob={pullRequest.isPassedAllCiJob}
69
72
  isCiStateSuccess={pullRequest.isCiStateSuccess}
70
73
  isBranchOutOfDate={pullRequest.isBranchOutOfDate}
@@ -0,0 +1,29 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ConsolePullRequestMergeableChip } from './ConsolePullRequestMergeableChip';
3
+
4
+ const meta: Meta<typeof ConsolePullRequestMergeableChip> = {
5
+ title: 'Console/ConsolePullRequestMergeableChip',
6
+ component: ConsolePullRequestMergeableChip,
7
+ };
8
+
9
+ export default meta;
10
+
11
+ type Story = StoryObj<typeof ConsolePullRequestMergeableChip>;
12
+
13
+ export const Mergeable: Story = {
14
+ args: {
15
+ mergeableStatus: 'MERGEABLE',
16
+ },
17
+ };
18
+
19
+ export const Conflicting: Story = {
20
+ args: {
21
+ mergeableStatus: 'CONFLICTING',
22
+ },
23
+ };
24
+
25
+ export const CheckingMergeStatus: Story = {
26
+ args: {
27
+ mergeableStatus: 'UNKNOWN',
28
+ },
29
+ };
@@ -0,0 +1,30 @@
1
+ import { render } from '@testing-library/react';
2
+ import { ConsolePullRequestMergeableChip } from './ConsolePullRequestMergeableChip';
3
+
4
+ describe('ConsolePullRequestMergeableChip', () => {
5
+ it('renders the green no-conflict chip when the pull request is mergeable', () => {
6
+ const { getByText, queryByText } = render(
7
+ <ConsolePullRequestMergeableChip mergeableStatus="MERGEABLE" />,
8
+ );
9
+ expect(getByText('No conflict')).toBeInTheDocument();
10
+ expect(queryByText('Conflict')).toBeNull();
11
+ expect(queryByText('Checking merge status')).toBeNull();
12
+ });
13
+
14
+ it('renders the red conflict chip when the pull request is conflicting', () => {
15
+ const { getByText, queryByText } = render(
16
+ <ConsolePullRequestMergeableChip mergeableStatus="CONFLICTING" />,
17
+ );
18
+ expect(getByText('Conflict')).toBeInTheDocument();
19
+ expect(queryByText('No conflict')).toBeNull();
20
+ });
21
+
22
+ it('renders the gray checking chip when the merge status is unknown', () => {
23
+ const { getByText, queryByText } = render(
24
+ <ConsolePullRequestMergeableChip mergeableStatus="UNKNOWN" />,
25
+ );
26
+ expect(getByText('Checking merge status')).toBeInTheDocument();
27
+ expect(queryByText('Conflict')).toBeNull();
28
+ expect(queryByText('No conflict')).toBeNull();
29
+ });
30
+ });
@@ -0,0 +1,50 @@
1
+ import { colorFromEnum } from '../../logic/colors';
2
+ import type { ConsoleColor, ConsoleMergeableStatus } from '../../logic/types';
3
+
4
+ export type ConsolePullRequestMergeableChipProps = {
5
+ mergeableStatus: ConsoleMergeableStatus;
6
+ };
7
+
8
+ const MERGEABLE_BADGE: Record<
9
+ ConsoleMergeableStatus,
10
+ { color: ConsoleColor; label: string; title: string; modifier: string }
11
+ > = {
12
+ MERGEABLE: {
13
+ color: 'GREEN',
14
+ label: 'No conflict',
15
+ title: 'This pull request has no merge conflicts',
16
+ modifier: 'console-detail-mergeable-chip-ok',
17
+ },
18
+ CONFLICTING: {
19
+ color: 'RED',
20
+ label: 'Conflict',
21
+ title: 'This pull request has merge conflicts',
22
+ modifier: 'console-detail-mergeable-chip-conflict',
23
+ },
24
+ UNKNOWN: {
25
+ color: 'GRAY',
26
+ label: 'Checking merge status',
27
+ title: 'GitHub has not finished computing the merge status yet',
28
+ modifier: 'console-detail-mergeable-chip-unknown',
29
+ },
30
+ };
31
+
32
+ export const ConsolePullRequestMergeableChip = ({
33
+ mergeableStatus,
34
+ }: ConsolePullRequestMergeableChipProps) => {
35
+ const badge = MERGEABLE_BADGE[mergeableStatus];
36
+ const palette = colorFromEnum(badge.color);
37
+ return (
38
+ <span
39
+ className={`console-detail-status-chip console-detail-mergeable-chip ${badge.modifier}`}
40
+ style={{
41
+ color: palette.fg,
42
+ borderColor: palette.border,
43
+ backgroundColor: palette.bg,
44
+ }}
45
+ title={badge.title}
46
+ >
47
+ {badge.label}
48
+ </span>
49
+ );
50
+ };
@@ -10,9 +10,8 @@ export default meta;
10
10
 
11
11
  type Story = StoryObj<typeof ConsolePullRequestStatusBadges>;
12
12
 
13
- export const PassingAndMergeable: Story = {
13
+ export const Passing: Story = {
14
14
  args: {
15
- mergeableStatus: 'MERGEABLE',
16
15
  isPassedAllCiJob: true,
17
16
  isCiStateSuccess: true,
18
17
  isBranchOutOfDate: false,
@@ -22,7 +21,6 @@ export const PassingAndMergeable: Story = {
22
21
 
23
22
  export const FailingWithMissingChecks: Story = {
24
23
  args: {
25
- mergeableStatus: 'MERGEABLE',
26
24
  isPassedAllCiJob: false,
27
25
  isCiStateSuccess: false,
28
26
  isBranchOutOfDate: false,
@@ -30,22 +28,11 @@ export const FailingWithMissingChecks: Story = {
30
28
  },
31
29
  };
32
30
 
33
- export const ConflictingAndOutOfDate: Story = {
31
+ export const FailingAndOutOfDate: Story = {
34
32
  args: {
35
- mergeableStatus: 'CONFLICTING',
36
33
  isPassedAllCiJob: false,
37
34
  isCiStateSuccess: false,
38
35
  isBranchOutOfDate: true,
39
36
  missingRequiredCheckNames: ['build', 'test'],
40
37
  },
41
38
  };
42
-
43
- export const CheckingMergeStatus: Story = {
44
- args: {
45
- mergeableStatus: 'UNKNOWN',
46
- isPassedAllCiJob: true,
47
- isCiStateSuccess: true,
48
- isBranchOutOfDate: false,
49
- missingRequiredCheckNames: [],
50
- },
51
- };
@@ -5,7 +5,6 @@ describe('ConsolePullRequestStatusBadges', () => {
5
5
  it('renders a passing CI badge when all CI jobs passed and the CI state is success', () => {
6
6
  const { getByText, queryByText } = render(
7
7
  <ConsolePullRequestStatusBadges
8
- mergeableStatus="MERGEABLE"
9
8
  isPassedAllCiJob={true}
10
9
  isCiStateSuccess={true}
11
10
  isBranchOutOfDate={false}
@@ -19,7 +18,6 @@ describe('ConsolePullRequestStatusBadges', () => {
19
18
  it('renders a failing CI badge with missing required checks', () => {
20
19
  const { getByText } = render(
21
20
  <ConsolePullRequestStatusBadges
22
- mergeableStatus="MERGEABLE"
23
21
  isPassedAllCiJob={false}
24
22
  isCiStateSuccess={true}
25
23
  isBranchOutOfDate={false}
@@ -30,54 +28,9 @@ describe('ConsolePullRequestStatusBadges', () => {
30
28
  expect(getByText(/missing: build, test/)).toBeInTheDocument();
31
29
  });
32
30
 
33
- it('renders the green no-conflict badge when the pull request is mergeable', () => {
34
- const { getByText, queryByText } = render(
35
- <ConsolePullRequestStatusBadges
36
- mergeableStatus="MERGEABLE"
37
- isPassedAllCiJob={true}
38
- isCiStateSuccess={true}
39
- isBranchOutOfDate={false}
40
- missingRequiredCheckNames={[]}
41
- />,
42
- );
43
- expect(getByText('No conflict')).toBeInTheDocument();
44
- expect(queryByText('Conflict')).toBeNull();
45
- expect(queryByText('Checking merge status')).toBeNull();
46
- });
47
-
48
- it('renders the red conflict badge when the pull request is conflicting', () => {
49
- const { getByText, queryByText } = render(
50
- <ConsolePullRequestStatusBadges
51
- mergeableStatus="CONFLICTING"
52
- isPassedAllCiJob={true}
53
- isCiStateSuccess={true}
54
- isBranchOutOfDate={false}
55
- missingRequiredCheckNames={[]}
56
- />,
57
- );
58
- expect(getByText('Conflict')).toBeInTheDocument();
59
- expect(queryByText('No conflict')).toBeNull();
60
- });
61
-
62
- it('renders the gray checking badge when the merge status is unknown', () => {
63
- const { getByText, queryByText } = render(
64
- <ConsolePullRequestStatusBadges
65
- mergeableStatus="UNKNOWN"
66
- isPassedAllCiJob={true}
67
- isCiStateSuccess={true}
68
- isBranchOutOfDate={false}
69
- missingRequiredCheckNames={[]}
70
- />,
71
- );
72
- expect(getByText('Checking merge status')).toBeInTheDocument();
73
- expect(queryByText('Conflict')).toBeNull();
74
- expect(queryByText('No conflict')).toBeNull();
75
- });
76
-
77
31
  it('renders the out-of-date badge when the branch is behind the base branch', () => {
78
32
  const { getByText } = render(
79
33
  <ConsolePullRequestStatusBadges
80
- mergeableStatus="MERGEABLE"
81
34
  isPassedAllCiJob={true}
82
35
  isCiStateSuccess={true}
83
36
  isBranchOutOfDate={true}
@@ -90,7 +43,6 @@ describe('ConsolePullRequestStatusBadges', () => {
90
43
  it('treats a successful CI state but unfinished jobs as failing', () => {
91
44
  const { getByText } = render(
92
45
  <ConsolePullRequestStatusBadges
93
- mergeableStatus="MERGEABLE"
94
46
  isPassedAllCiJob={false}
95
47
  isCiStateSuccess={true}
96
48
  isBranchOutOfDate={false}
@@ -1,8 +1,7 @@
1
1
  import { colorFromEnum } from '../../logic/colors';
2
- import type { ConsoleColor, ConsoleMergeableStatus } from '../../logic/types';
2
+ import type { ConsoleColor } from '../../logic/types';
3
3
 
4
4
  export type ConsolePullRequestStatusBadgesProps = {
5
- mergeableStatus: ConsoleMergeableStatus;
6
5
  isPassedAllCiJob: boolean;
7
6
  isCiStateSuccess: boolean;
8
7
  isBranchOutOfDate: boolean;
@@ -18,32 +17,7 @@ const badgeStyle = (color: ConsoleColor) => {
18
17
  };
19
18
  };
20
19
 
21
- const MERGEABLE_BADGE: Record<
22
- ConsoleMergeableStatus,
23
- { color: ConsoleColor; label: string; title: string; modifier: string }
24
- > = {
25
- MERGEABLE: {
26
- color: 'GREEN',
27
- label: 'No conflict',
28
- title: 'This pull request has no merge conflicts',
29
- modifier: 'console-detail-mergeable-chip-ok',
30
- },
31
- CONFLICTING: {
32
- color: 'RED',
33
- label: 'Conflict',
34
- title: 'This pull request has merge conflicts',
35
- modifier: 'console-detail-mergeable-chip-conflict',
36
- },
37
- UNKNOWN: {
38
- color: 'GRAY',
39
- label: 'Checking merge status',
40
- title: 'GitHub has not finished computing the merge status yet',
41
- modifier: 'console-detail-mergeable-chip-unknown',
42
- },
43
- };
44
-
45
20
  export const ConsolePullRequestStatusBadges = ({
46
- mergeableStatus,
47
21
  isPassedAllCiJob,
48
22
  isCiStateSuccess,
49
23
  isBranchOutOfDate,
@@ -56,7 +30,6 @@ export const ConsolePullRequestStatusBadges = ({
56
30
  !ciPassing && missingRequiredCheckNames.length > 0
57
31
  ? `missing: ${missingRequiredCheckNames.join(', ')}`
58
32
  : null;
59
- const mergeableBadge = MERGEABLE_BADGE[mergeableStatus];
60
33
 
61
34
  return (
62
35
  <span className="console-detail-pr-status">
@@ -73,13 +46,6 @@ export const ConsolePullRequestStatusBadges = ({
73
46
  </span>
74
47
  )}
75
48
  </span>
76
- <span
77
- className={`console-detail-status-chip console-detail-mergeable-chip ${mergeableBadge.modifier}`}
78
- style={badgeStyle(mergeableBadge.color)}
79
- title={mergeableBadge.title}
80
- >
81
- {mergeableBadge.label}
82
- </span>
83
49
  {isBranchOutOfDate && (
84
50
  <span
85
51
  className="console-detail-status-chip console-detail-outofdate-chip"
@@ -89,6 +89,21 @@ describe('useConsoleOperations', () => {
89
89
  });
90
90
  });
91
91
 
92
+ it('posts an unnecessary review carrying the comment the linked issue receives', async () => {
93
+ const fetchMock = captureFetch();
94
+ const { result } = setup();
95
+ await act(async () => {
96
+ await result.current.operations.reviewPullRequest(
97
+ prItem,
98
+ prItem.url,
99
+ 'unnecessary',
100
+ );
101
+ });
102
+ expect(lastBody(fetchMock)).toMatchObject({
103
+ issueCommentBody: `The pull request for this issue was unnecessary and has been closed: ${prItem.url}\n\nDo not create it again.`,
104
+ });
105
+ });
106
+
92
107
  it('posts an approve review and marks the item done in the overlay', async () => {
93
108
  captureFetch();
94
109
  const { result } = setup();
@@ -12,6 +12,7 @@ import {
12
12
  } from '../lib/consoleApi';
13
13
  import {
14
14
  buildRequestChangesBody,
15
+ buildUnnecessaryIssueCommentBody,
15
16
  type ConsoleCloseAction,
16
17
  type ConsoleNextActionDateAction,
17
18
  type ConsolePendingReviewComment,
@@ -119,6 +120,7 @@ const reviewRequest = (
119
120
  projectItemId: item.projectItemId,
120
121
  issueUrl: item.url,
121
122
  commentBody: UNNECESSARY_COMMENT_BODY,
123
+ issueCommentBody: buildUnnecessaryIssueCommentBody(prUrl),
122
124
  };
123
125
  };
124
126
 
@@ -25,6 +25,7 @@ export type ConsoleReviewRequest = {
25
25
  projectItemId: string;
26
26
  issueUrl?: string;
27
27
  commentBody?: string;
28
+ issueCommentBody?: string;
28
29
  changedFilePath?: string;
29
30
  line?: number;
30
31
  side?: ConsoleReviewCommentSide;
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  buildRequestChangesBody,
3
+ buildUnnecessaryIssueCommentBody,
3
4
  IN_TMUX_BY_HUMAN_NAME,
4
5
  isManualTriageTab,
5
6
  STATUS_BUTTON_NAMES,
@@ -16,6 +17,14 @@ describe('operation constants', () => {
16
17
  expect(UNNECESSARY_COMMENT_BODY).toBe('This pull request is unnecessary.');
17
18
  });
18
19
 
20
+ it('builds an issue comment body that names the closed pull request and forbids creating it again', () => {
21
+ expect(
22
+ buildUnnecessaryIssueCommentBody('https://github.com/o/r/pull/1'),
23
+ ).toBe(
24
+ 'The pull request for this issue was unnecessary and has been closed: https://github.com/o/r/pull/1\n\nDo not create it again.',
25
+ );
26
+ });
27
+
19
28
  it('lists the status buttons left to right', () => {
20
29
  expect(STATUS_BUTTON_NAMES).toEqual([
21
30
  'In Tmux by agent',
@@ -4,6 +4,9 @@ import type { ConsoleFieldOption, ConsoleTabName } from './types';
4
4
  export const TOTALLY_WRONG_COMMENT_BODY = 'totally wrong';
5
5
  export const UNNECESSARY_COMMENT_BODY = 'This pull request is unnecessary.';
6
6
 
7
+ export const buildUnnecessaryIssueCommentBody = (prUrl: string): string =>
8
+ `The pull request for this issue was unnecessary and has been closed: ${prUrl}\n\nDo not create it again.`;
9
+
7
10
  export type ConsolePendingReviewComment = {
8
11
  path: string;
9
12
  line: number;