github-issue-tower-defence-management 1.144.0 → 1.144.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.
Files changed (26) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/adapter/entry-points/console/ui-dist/assets/index-BYoG8oQb.js +82 -0
  3. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  4. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +28 -4
  5. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +3 -0
  8. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.test.ts +1 -0
  9. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +2 -0
  10. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +39 -0
  11. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +14 -1
  12. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.test.ts +1 -0
  13. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +1 -0
  14. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +1 -0
  15. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +42 -1
  16. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +7 -2
  17. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +7 -0
  18. package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +5 -0
  19. package/src/adapter/entry-points/console/ui-dist/assets/index-BYoG8oQb.js +82 -0
  20. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  21. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +92 -0
  22. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +52 -4
  23. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +3 -0
  24. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
  25. package/bin/adapter/entry-points/console/ui-dist/assets/index-y6LyqXlu.js +0 -82
  26. package/src/adapter/entry-points/console/ui-dist/assets/index-y6LyqXlu.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-y6LyqXlu.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-BYoG8oQb.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-CKX2ny-z.css">
9
9
  </head>
10
10
  <body>
@@ -152,6 +152,36 @@ describe('GenerateConsoleListsUseCase', () => {
152
152
  const result = run([makeIssue({ status: 'Unread' })]);
153
153
  expect(result.unread.items).toHaveLength(1);
154
154
  });
155
+
156
+ it('accepts an unread issue that has no assignee', () => {
157
+ const result = run([makeIssue({ status: 'Unread', assignees: [] })]);
158
+ expect(result.unread.items).toHaveLength(1);
159
+ });
160
+
161
+ it('accepts an unread pull request that has no assignee', () => {
162
+ const result = run([
163
+ makeIssue({ status: 'Unread', assignees: [], isPr: true }),
164
+ ]);
165
+ expect(result.unread.items).toHaveLength(1);
166
+ });
167
+
168
+ it('accepts a todo-by-human issue that has no assignee', () => {
169
+ const result = run([
170
+ makeIssue({ status: 'Todo by human', assignees: [] }),
171
+ ]);
172
+ expect(result['todo-by-human'].items).toHaveLength(1);
173
+ });
174
+
175
+ it('keeps rejecting an unassigned issue that is blocked by a depended issue url', () => {
176
+ const result = run([
177
+ makeIssue({
178
+ status: 'Unread',
179
+ assignees: [],
180
+ dependedIssueUrls: ['https://github.com/demo/repo/issues/99'],
181
+ }),
182
+ ]);
183
+ expect(result.unread.items).toHaveLength(0);
184
+ });
155
185
  });
156
186
 
157
187
  describe('per-tab selectors', () => {
@@ -378,6 +408,7 @@ describe('GenerateConsoleListsUseCase', () => {
378
408
  'nextActionHour',
379
409
  'number',
380
410
  'projectItemId',
411
+ 'relatedOpenPullRequestUrls',
381
412
  'repo',
382
413
  'status',
383
414
  'story',
@@ -582,6 +613,67 @@ describe('GenerateConsoleListsUseCase', () => {
582
613
  });
583
614
  });
584
615
 
616
+ describe('related open pull requests', () => {
617
+ const awaitingQualityCheckIssue = (): Issue =>
618
+ makeIssue({ status: 'Awaiting Quality Check' });
619
+
620
+ it('projects the open pull request that closes the issue', () => {
621
+ const issue = awaitingQualityCheckIssue();
622
+ const pullRequest = makeIssue({
623
+ status: 'Awaiting Quality Check',
624
+ isPr: true,
625
+ url: 'https://github.com/demo/repo/pull/501',
626
+ closingIssueReferenceUrls: [issue.url],
627
+ });
628
+ const result = run([issue, pullRequest]);
629
+ const projected = result.prs.items.find((item) => item.url === issue.url);
630
+ expect(projected?.relatedOpenPullRequestUrls).toEqual([
631
+ 'https://github.com/demo/repo/pull/501',
632
+ ]);
633
+ });
634
+
635
+ it('ignores a closed pull request that closes the issue', () => {
636
+ const issue = awaitingQualityCheckIssue();
637
+ const pullRequest = makeIssue({
638
+ isPr: true,
639
+ isClosed: true,
640
+ url: 'https://github.com/demo/repo/pull/502',
641
+ closingIssueReferenceUrls: [issue.url],
642
+ });
643
+ const result = run([issue, pullRequest]);
644
+ const projected = result.prs.items.find((item) => item.url === issue.url);
645
+ expect(projected?.relatedOpenPullRequestUrls).toEqual([]);
646
+ });
647
+
648
+ it('collects the linkage from a pull request that no tab shows', () => {
649
+ const issue = awaitingQualityCheckIssue();
650
+ const pullRequest = makeIssue({
651
+ status: 'In Tmux by agent',
652
+ assignees: ['another-person'],
653
+ isPr: true,
654
+ url: 'https://github.com/demo/repo/pull/503',
655
+ closingIssueReferenceUrls: [issue.url],
656
+ });
657
+ const result = run([issue, pullRequest]);
658
+ const projected = result.prs.items.find((item) => item.url === issue.url);
659
+ expect(projected?.relatedOpenPullRequestUrls).toEqual([
660
+ 'https://github.com/demo/repo/pull/503',
661
+ ]);
662
+ });
663
+
664
+ it('leaves the list empty when no open pull request closes the issue', () => {
665
+ const issue = awaitingQualityCheckIssue();
666
+ const unrelatedPullRequest = makeIssue({
667
+ isPr: true,
668
+ url: 'https://github.com/demo/repo/pull/504',
669
+ closingIssueReferenceUrls: ['https://github.com/demo/repo/issues/999'],
670
+ });
671
+ const result = run([issue, unrelatedPullRequest]);
672
+ const projected = result.prs.items.find((item) => item.url === issue.url);
673
+ expect(projected?.relatedOpenPullRequestUrls).toEqual([]);
674
+ });
675
+ });
676
+
585
677
  describe('project without a story field', () => {
586
678
  const projectNoStory = baseProject(null);
587
679
 
@@ -25,6 +25,7 @@ export type ConsoleListItem = {
25
25
  dependedIssueUrls: string[];
26
26
  labels: string[];
27
27
  createdAt: string;
28
+ relatedOpenPullRequestUrls: string[];
28
29
  };
29
30
 
30
31
  export type ConsoleFieldOption = {
@@ -96,6 +97,9 @@ export class GenerateConsoleListsUseCase {
96
97
  const storyOrder = storyOptions.map((option) => option.name);
97
98
  const statusOptions = project.status.statuses;
98
99
 
100
+ const relatedOpenPullRequestUrlsByIssueUrl =
101
+ this.buildRelatedOpenPullRequestUrlsByIssueUrl(issues);
102
+
99
103
  const visibleIssues = issues.filter(
100
104
  (issue) =>
101
105
  issue.status?.toLowerCase() !==
@@ -117,7 +121,14 @@ export class GenerateConsoleListsUseCase {
117
121
  storyOrder,
118
122
  storyColors: this.buildStoryColorsObject(storyOptions),
119
123
  items: this.sortByStoryOrder(
120
- sourceIssues.filter(selector).map((issue) => this.projectItem(issue)),
124
+ sourceIssues
125
+ .filter(selector)
126
+ .map((issue) =>
127
+ this.projectItem(
128
+ issue,
129
+ relatedOpenPullRequestUrlsByIssueUrl.get(issue.url) ?? [],
130
+ ),
131
+ ),
121
132
  storyOrder,
122
133
  ),
123
134
  });
@@ -183,7 +194,12 @@ export class GenerateConsoleListsUseCase {
183
194
  issue.status?.toLowerCase() !==
184
195
  IN_TMUX_BY_AGENT_STATUS_NAME.toLowerCase(),
185
196
  )
186
- .map((issue) => this.projectItem(issue)),
197
+ .map((issue) =>
198
+ this.projectItem(
199
+ issue,
200
+ relatedOpenPullRequestUrlsByIssueUrl.get(issue.url) ?? [],
201
+ ),
202
+ ),
187
203
  storyOrder,
188
204
  ),
189
205
  },
@@ -192,11 +208,17 @@ export class GenerateConsoleListsUseCase {
192
208
 
193
209
  private isActionable = (issue: Issue, assigneeLogin: string): boolean =>
194
210
  issue.isClosed === false &&
195
- issue.assignees.includes(assigneeLogin) &&
211
+ this.isOwnedByNobodyElse(issue, assigneeLogin) &&
196
212
  issue.dependedIssueUrls.length === 0 &&
197
213
  issue.nextActionDate === null &&
198
214
  issue.nextActionHour === null;
199
215
 
216
+ private isOwnedByNobodyElse = (
217
+ issue: Issue,
218
+ assigneeLogin: string,
219
+ ): boolean =>
220
+ issue.assignees.length === 0 || issue.assignees.includes(assigneeLogin);
221
+
200
222
  private workflowBlockerSelector = (
201
223
  workflowBlockerStoryName: string | null,
202
224
  ): ((issue: Issue) => boolean) => {
@@ -208,7 +230,32 @@ export class GenerateConsoleListsUseCase {
208
230
  issue.story !== null && issue.story.toLowerCase() === target;
209
231
  };
210
232
 
211
- private projectItem = (issue: Issue): ConsoleListItem => ({
233
+ private buildRelatedOpenPullRequestUrlsByIssueUrl = (
234
+ issues: Issue[],
235
+ ): Map<string, string[]> => {
236
+ const urlsByIssueUrl = new Map<string, string[]>();
237
+ for (const issue of issues) {
238
+ if (!issue.isPr || issue.isClosed) {
239
+ continue;
240
+ }
241
+ for (const referencedIssueUrl of issue.closingIssueReferenceUrls) {
242
+ const existing = urlsByIssueUrl.get(referencedIssueUrl);
243
+ if (existing === undefined) {
244
+ urlsByIssueUrl.set(referencedIssueUrl, [issue.url]);
245
+ continue;
246
+ }
247
+ if (!existing.includes(issue.url)) {
248
+ existing.push(issue.url);
249
+ }
250
+ }
251
+ }
252
+ return urlsByIssueUrl;
253
+ };
254
+
255
+ private projectItem = (
256
+ issue: Issue,
257
+ relatedOpenPullRequestUrls: string[],
258
+ ): ConsoleListItem => ({
212
259
  number: issue.number,
213
260
  title: issue.title,
214
261
  url: issue.url,
@@ -225,6 +272,7 @@ export class GenerateConsoleListsUseCase {
225
272
  dependedIssueUrls: issue.dependedIssueUrls,
226
273
  labels: issue.labels,
227
274
  createdAt: issue.createdAt.toISOString(),
275
+ relatedOpenPullRequestUrls,
228
276
  });
229
277
 
230
278
  private buildFieldOptions = (
@@ -17,6 +17,7 @@ export type ConsoleListItem = {
17
17
  dependedIssueUrls: string[];
18
18
  labels: string[];
19
19
  createdAt: string;
20
+ relatedOpenPullRequestUrls: string[];
20
21
  };
21
22
  export type ConsoleFieldOption = {
22
23
  id: string;
@@ -62,7 +63,9 @@ export type GenerateConsoleListsInput = {
62
63
  export declare class GenerateConsoleListsUseCase {
63
64
  run: (input: GenerateConsoleListsInput) => ConsoleLists;
64
65
  private isActionable;
66
+ private isOwnedByNobodyElse;
65
67
  private workflowBlockerSelector;
68
+ private buildRelatedOpenPullRequestUrlsByIssueUrl;
66
69
  private projectItem;
67
70
  private buildFieldOptions;
68
71
  private buildStoryColorsObject;
@@ -1 +1 @@
1
- {"version":3,"file":"GenerateConsoleListsUseCase.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/console/GenerateConsoleListsUseCase.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GenerateConsoleListsUseCase.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/console/GenerateConsoleListsUseCase.ts"],"names":[],"mappings":"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"}