github-issue-tower-defence-management 1.174.2 → 1.175.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/adapter/entry-points/console/consoleDoneStore.js +0 -2
  3. package/bin/adapter/entry-points/console/consoleDoneStore.js.map +1 -1
  4. package/bin/adapter/entry-points/console/consoleTabNames.js +0 -1
  5. package/bin/adapter/entry-points/console/consoleTabNames.js.map +1 -1
  6. package/bin/adapter/entry-points/console/dashboardComposeService.js +2 -4
  7. package/bin/adapter/entry-points/console/dashboardComposeService.js.map +1 -1
  8. package/bin/adapter/entry-points/console/ui-dist/assets/{index-g4SqIzCh.js → index-BhDothvr.js} +1 -1
  9. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  10. package/bin/domain/entities/WorkflowStatus.js +1 -6
  11. package/bin/domain/entities/WorkflowStatus.js.map +1 -1
  12. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +4 -5
  13. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  14. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +0 -39
  15. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  16. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +4 -5
  17. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  18. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +15 -1
  19. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -1
  20. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +0 -2
  21. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
  22. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js +2 -3
  23. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js.map +1 -1
  24. package/bin/domain/usecases/dashboard/GenerateDashboardRowUseCase.js +0 -1
  25. package/bin/domain/usecases/dashboard/GenerateDashboardRowUseCase.js.map +1 -1
  26. package/bin/domain/usecases/findLastAgentDeclaringReport.js +12 -0
  27. package/bin/domain/usecases/findLastAgentDeclaringReport.js.map +1 -0
  28. package/package.json +1 -1
  29. package/src/adapter/entry-points/console/consoleDoneStore.ts +0 -2
  30. package/src/adapter/entry-points/console/consoleTabNames.ts +0 -1
  31. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +4 -8
  32. package/src/adapter/entry-points/console/dashboardComposeService.ts +1 -3
  33. package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +12 -17
  34. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +4 -32
  35. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +1 -18
  36. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +3 -7
  37. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +2 -4
  38. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +1 -1
  39. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +6 -8
  40. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +8 -17
  41. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +0 -2
  42. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +3 -3
  43. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +5 -6
  44. package/src/adapter/entry-points/console/ui-dist/assets/{index-g4SqIzCh.js → index-BhDothvr.js} +1 -1
  45. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  46. package/src/adapter/entry-points/console/webServer.test.ts +4 -6
  47. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +30 -26
  48. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +5 -6
  49. package/src/domain/entities/WorkflowStatus.ts +0 -5
  50. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +32 -0
  51. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +7 -8
  52. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +198 -863
  53. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +0 -68
  54. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +8 -11
  55. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +89 -51
  56. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +30 -2
  57. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +62 -72
  58. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +0 -8
  59. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +19 -28
  60. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +2 -3
  61. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +11 -12
  62. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.ts +0 -3
  63. package/src/domain/usecases/findLastAgentDeclaringReport.test.ts +71 -0
  64. package/src/domain/usecases/findLastAgentDeclaringReport.ts +17 -0
  65. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +2 -2
  66. package/types/adapter/entry-points/console/consoleDoneStore.d.ts.map +1 -1
  67. package/types/adapter/entry-points/console/consoleTabNames.d.ts.map +1 -1
  68. package/types/adapter/entry-points/console/dashboardComposeService.d.ts.map +1 -1
  69. package/types/domain/entities/WorkflowStatus.d.ts +0 -1
  70. package/types/domain/entities/WorkflowStatus.d.ts.map +1 -1
  71. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  72. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  73. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  74. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts +1 -0
  75. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -1
  76. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +1 -2
  77. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
  78. package/types/domain/usecases/dashboard/ComposeDashboardUseCase.d.ts.map +1 -1
  79. package/types/domain/usecases/dashboard/GenerateDashboardRowUseCase.d.ts +0 -1
  80. package/types/domain/usecases/dashboard/GenerateDashboardRowUseCase.d.ts.map +1 -1
  81. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts +5 -0
  82. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts.map +1 -0
@@ -10,7 +10,6 @@ import { isAuthorAuthorizedForAutoStatusCheck } from './isAuthorAuthorizedForAut
10
10
  import {
11
11
  AWAITING_QUALITY_CHECK_STATUS_NAME,
12
12
  AWAITING_WORKSPACE_STATUS_NAME,
13
- DEFAULT_STATUS_NAME,
14
13
  } from '../entities/WorkflowStatus';
15
14
 
16
15
  // GitHub rejects field mutations against archived project items with
@@ -96,10 +95,6 @@ export class RevertNotReadyReviewQueueIssueUseCase {
96
95
  const relatedOpenPrUrlsByIssueUrl =
97
96
  this.buildRelatedOpenPrUrlsByIssueUrl(issues);
98
97
 
99
- const unreadPullRequests = issues.filter(
100
- (issue) => issue.status === DEFAULT_STATUS_NAME && issue.isPr,
101
- );
102
-
103
98
  const labelsNotRequiringPullRequest =
104
99
  resolveLabelsNotRequiringPullRequest(params);
105
100
  const willBeEvaluated = (item: Issue): boolean =>
@@ -117,9 +112,6 @@ export class RevertNotReadyReviewQueueIssueUseCase {
117
112
  .flatMap(
118
113
  (issue) => relatedOpenPrUrlsByIssueUrl.get(issue.url) ?? [],
119
114
  ),
120
- ...unreadPullRequests
121
- .filter(willBeEvaluated)
122
- .map((pullRequest) => pullRequest.url),
123
115
  ]),
124
116
  ),
125
117
  );
@@ -194,66 +186,6 @@ export class RevertNotReadyReviewQueueIssueUseCase {
194
186
  throw error;
195
187
  }
196
188
  }
197
-
198
- const projectStory = project.story;
199
-
200
- for (const pullRequest of unreadPullRequests) {
201
- if (
202
- !isAuthorAuthorizedForAutoStatusCheck(
203
- pullRequest.author,
204
- allowedIssueAuthors,
205
- )
206
- ) {
207
- continue;
208
- }
209
-
210
- try {
211
- const { rejections } = await this.issueRejectionEvaluator.evaluate(
212
- pullRequest,
213
- labelsNotRequiringPullRequest,
214
- { resolvedOpenPrByUrl },
215
- );
216
- if (rejections.length > 0) {
217
- if (!pullRequest.assignees.includes(params.manager)) {
218
- continue;
219
- }
220
- try {
221
- await this.issueRepository.updateStatus(
222
- project,
223
- pullRequest,
224
- awaitingWorkspaceStatusOption.id,
225
- );
226
- } catch (error) {
227
- if (isArchivedProjectItemError(error)) {
228
- console.warn(
229
- `RevertNotReadyReviewQueueIssueUseCase: project item is archived and cannot be updated, skipping revert. prUrl: ${pullRequest.url}`,
230
- );
231
- continue;
232
- }
233
- throw error;
234
- }
235
- if (projectStory) {
236
- await this.issueRepository.updateStory(
237
- { ...project, story: projectStory },
238
- pullRequest,
239
- projectStory.workflowManagementStory.id,
240
- );
241
- }
242
- await this.issueCommentRepository.createComment(
243
- pullRequest,
244
- `Auto Status Check: REJECTED\n${rejections.map((r) => `- ${r.detail}`).join('\n')}`,
245
- );
246
- }
247
- } catch (error) {
248
- if (isTimeoutError(error)) {
249
- console.warn(
250
- `RevertNotReadyReviewQueueIssueUseCase: request timed out, skipping pull request for this cycle. prUrl: ${pullRequest.url} error: ${error instanceof Error ? error.message : String(error)}`,
251
- );
252
- continue;
253
- }
254
- throw error;
255
- }
256
- }
257
189
  };
258
190
 
259
191
  // Derives, for each issue, the set of open pull request URLs that reference it
@@ -23,6 +23,7 @@ import {
23
23
  RETURNED_TO_AWAITING_WORKSPACE_MESSAGE_HEAD,
24
24
  } from './returnedToAwaitingWorkspaceMessage';
25
25
  import { extractNextStepAgent } from './extractNextStepAgent';
26
+ import { findLastAgentDeclaringReport } from './findLastAgentDeclaringReport';
26
27
  import { ensureAgentOptionAndGetId } from './ensureAgentOptionAndGetId';
27
28
  import { normalizeReportBody } from './normalizeReportBody';
28
29
 
@@ -349,17 +350,13 @@ export class RevertOrphanedPreparationUseCase {
349
350
  comments: Comment[],
350
351
  allowedIssueAuthors: string[] | null | undefined,
351
352
  ): string | null => {
352
- const lastReportComment = [...comments]
353
- .reverse()
354
- .find(
355
- (comment) =>
356
- isAuthorAuthorizedForAutoStatusCheck(
357
- comment.author,
358
- allowedIssueAuthors,
359
- ) && comment.content.startsWith('From: :robot:'),
360
- );
361
- return lastReportComment
362
- ? extractNextStepAgent(lastReportComment.content)
353
+ const lastAgentDeclaringReport = findLastAgentDeclaringReport(
354
+ comments,
355
+ (author) =>
356
+ isAuthorAuthorizedForAutoStatusCheck(author, allowedIssueAuthors),
357
+ );
358
+ return lastAgentDeclaringReport
359
+ ? extractNextStepAgent(lastAgentDeclaringReport.content)
363
360
  : null;
364
361
  };
365
362
 
@@ -7,7 +7,6 @@ import { Issue } from '../entities/Issue';
7
7
  import {
8
8
  AWAITING_QUALITY_CHECK_STATUS_NAME,
9
9
  AWAITING_WORKSPACE_STATUS_NAME,
10
- DEFAULT_STATUS_NAME,
11
10
  DONE_STATUS_NAME,
12
11
  FAILED_PREPARATION_STATUS_NAME,
13
12
  ICEBOX_STATUS_NAME,
@@ -80,9 +79,8 @@ const buildIssue = (overrides: Partial<Issue>): Issue => ({
80
79
  });
81
80
 
82
81
  describe('SetupTowerDefenceProjectUseCase', () => {
83
- it('should define exactly the 11 required statuses in the documented order with the documented colors and no descriptions', () => {
82
+ it('should define exactly the 10 required statuses in the documented order with the documented colors and no descriptions', () => {
84
83
  expect(REQUIRED_WORKFLOW_STATUSES).toEqual([
85
- { name: DEFAULT_STATUS_NAME, color: 'ORANGE' },
86
84
  { name: AWAITING_WORKSPACE_STATUS_NAME, color: 'BLUE' },
87
85
  { name: PREPARATION_STATUS_NAME, color: 'YELLOW' },
88
86
  { name: FAILED_PREPARATION_STATUS_NAME, color: 'RED' },
@@ -190,9 +188,9 @@ describe('SetupTowerDefenceProjectUseCase', () => {
190
188
  mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
191
189
  const statuses: FieldOption[] = [
192
190
  {
193
- id: 'unread-id',
194
- name: DEFAULT_STATUS_NAME,
195
- color: 'ORANGE',
191
+ id: 'aws-id',
192
+ name: AWAITING_WORKSPACE_STATUS_NAME,
193
+ color: 'BLUE',
196
194
  description: '',
197
195
  },
198
196
  {
@@ -222,13 +220,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
222
220
  project,
223
221
  [
224
222
  {
225
- id: 'unread-id',
226
- name: DEFAULT_STATUS_NAME,
227
- color: 'ORANGE',
228
- description: '',
229
- },
230
- {
231
- id: null,
223
+ id: 'aws-id',
232
224
  name: AWAITING_WORKSPACE_STATUS_NAME,
233
225
  color: 'BLUE',
234
226
  description: '',
@@ -328,7 +320,6 @@ describe('SetupTowerDefenceProjectUseCase', () => {
328
320
  expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
329
321
  const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
330
322
  expect(payload.map((status) => status.name)).toEqual([
331
- DEFAULT_STATUS_NAME,
332
323
  AWAITING_WORKSPACE_STATUS_NAME,
333
324
  PREPARATION_STATUS_NAME,
334
325
  FAILED_PREPARATION_STATUS_NAME,
@@ -369,7 +360,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
369
360
  expect(payload[1].color).toBe(REQUIRED_WORKFLOW_STATUSES[1].color);
370
361
  });
371
362
 
372
- it('should reuse the default template "Todo" option ID for Unread so a newly added item lands in Unread', async () => {
363
+ it('should reuse the default template "Todo" option ID for Awaiting Workspace so a newly added item lands in Awaiting Workspace', async () => {
373
364
  const mockProjectRepository =
374
365
  mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
375
366
  const mockIssueRepository =
@@ -411,8 +402,10 @@ describe('SetupTowerDefenceProjectUseCase', () => {
411
402
 
412
403
  expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
413
404
  const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
414
- const unreadEntry = payload.find((s) => s.name === DEFAULT_STATUS_NAME);
415
- expect(unreadEntry?.id).toBe('template-todo');
405
+ const awaitingWorkspaceEntry = payload.find(
406
+ (s) => s.name === AWAITING_WORKSPACE_STATUS_NAME,
407
+ );
408
+ expect(awaitingWorkspaceEntry?.id).toBe('template-todo');
416
409
  const todoByHumanEntry = payload.find((s) => s.name === TODO_STATUS_NAME);
417
410
  expect(todoByHumanEntry?.id).toBeNull();
418
411
  expect(payload.some((s) => s.name === LEGACY_TODO_STATUS_NAME)).toBe(false);
@@ -453,7 +446,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
453
446
  const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
454
447
  const inTmuxEntry = payload.find((s) => s.name === IN_TMUX_STATUS_NAME);
455
448
  expect(inTmuxEntry).toBeDefined();
456
- expect(inTmuxEntry?.id).toBe('id-7');
449
+ expect(inTmuxEntry?.id).toBe('id-6');
457
450
  expect(payload.some((s) => s.name === LEGACY_IN_TMUX_STATUS_NAME)).toBe(
458
451
  false,
459
452
  );
@@ -505,7 +498,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
505
498
  if (s.name === TODO_BY_AGENT_STATUS_NAME) {
506
499
  return { ...s, id: 'preexisting-todo-agent-id' };
507
500
  }
508
- if (s.name === DEFAULT_STATUS_NAME) {
501
+ if (s.name === AWAITING_WORKSPACE_STATUS_NAME) {
509
502
  return { ...s, description: 'stale description' };
510
503
  }
511
504
  return s;
@@ -583,7 +576,7 @@ describe('SetupTowerDefenceProjectUseCase', () => {
583
576
  if (s.name === IN_TMUX_BY_AGENT_STATUS_NAME) {
584
577
  return { ...s, id: 'preexisting-agent-id' };
585
578
  }
586
- if (s.name === DEFAULT_STATUS_NAME) {
579
+ if (s.name === AWAITING_WORKSPACE_STATUS_NAME) {
587
580
  return { ...s, description: 'stale description' };
588
581
  }
589
582
  return s;
@@ -657,49 +650,43 @@ describe('SetupTowerDefenceProjectUseCase', () => {
657
650
  const statuses: FieldOption[] = [
658
651
  {
659
652
  id: 'id-0',
660
- name: DEFAULT_STATUS_NAME,
661
- color: 'ORANGE',
662
- description: '',
663
- },
664
- {
665
- id: 'id-1',
666
653
  name: AWAITING_WORKSPACE_STATUS_NAME,
667
654
  color: 'BLUE',
668
655
  description: '',
669
656
  },
670
657
  {
671
- id: 'id-2',
658
+ id: 'id-1',
672
659
  name: PREPARATION_STATUS_NAME,
673
660
  color: 'YELLOW',
674
661
  description: '',
675
662
  },
676
663
  {
677
- id: 'id-3',
664
+ id: 'id-2',
678
665
  name: FAILED_PREPARATION_STATUS_NAME,
679
666
  color: 'RED',
680
667
  description: '',
681
668
  },
682
669
  {
683
- id: 'id-4',
670
+ id: 'id-3',
684
671
  name: AWAITING_QUALITY_CHECK_STATUS_NAME,
685
672
  color: 'GREEN',
686
673
  description: '',
687
674
  },
688
675
  {
689
- id: 'id-5',
676
+ id: 'id-4',
690
677
  name: LEGACY_TODO_STATUS_NAME,
691
678
  color: 'PINK',
692
679
  description: '',
693
680
  },
694
- { id: 'id-6', name: PC_TODO_STATUS_NAME, color: 'PINK', description: '' },
681
+ { id: 'id-5', name: PC_TODO_STATUS_NAME, color: 'PINK', description: '' },
695
682
  {
696
- id: 'id-7',
683
+ id: 'id-6',
697
684
  name: LEGACY_IN_TMUX_STATUS_NAME,
698
685
  color: 'RED',
699
686
  description: '',
700
687
  },
701
- { id: 'id-8', name: DONE_STATUS_NAME, color: 'PURPLE', description: '' },
702
- { id: 'id-9', name: ICEBOX_STATUS_NAME, color: 'GRAY', description: '' },
688
+ { id: 'id-7', name: DONE_STATUS_NAME, color: 'PURPLE', description: '' },
689
+ { id: 'id-8', name: ICEBOX_STATUS_NAME, color: 'GRAY', description: '' },
703
690
  ];
704
691
  const project = buildProject(statuses);
705
692
  mockProjectRepository.getByUrl.mockResolvedValue(project);
@@ -720,7 +707,6 @@ describe('SetupTowerDefenceProjectUseCase', () => {
720
707
  const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
721
708
 
722
709
  expect(payload.map((s) => s.name)).toEqual([
723
- DEFAULT_STATUS_NAME,
724
710
  AWAITING_WORKSPACE_STATUS_NAME,
725
711
  PREPARATION_STATUS_NAME,
726
712
  FAILED_PREPARATION_STATUS_NAME,
@@ -733,9 +719,9 @@ describe('SetupTowerDefenceProjectUseCase', () => {
733
719
  ICEBOX_STATUS_NAME,
734
720
  ]);
735
721
 
736
- expect(payload.find((s) => s.name === TODO_STATUS_NAME)?.id).toBe('id-5');
722
+ expect(payload.find((s) => s.name === TODO_STATUS_NAME)?.id).toBe('id-4');
737
723
  expect(payload.find((s) => s.name === IN_TMUX_STATUS_NAME)?.id).toBe(
738
- 'id-7',
724
+ 'id-6',
739
725
  );
740
726
  expect(payload.some((s) => s.name === PC_TODO_STATUS_NAME)).toBe(false);
741
727
  expect(payload.some((s) => s.name === LEGACY_TODO_STATUS_NAME)).toBe(false);
@@ -751,12 +737,6 @@ describe('SetupTowerDefenceProjectUseCase', () => {
751
737
  mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
752
738
  const todoStatusId = 'todo-status-id';
753
739
  const statuses: FieldOption[] = [
754
- {
755
- id: 'id-0',
756
- name: DEFAULT_STATUS_NAME,
757
- color: 'ORANGE',
758
- description: '',
759
- },
760
740
  {
761
741
  id: 'atb-id',
762
742
  name: LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
@@ -858,7 +838,6 @@ describe('SetupTowerDefenceProjectUseCase', () => {
858
838
  ),
859
839
  ).toBe(false);
860
840
  expect(payload.map((s) => s.name)).toEqual([
861
- DEFAULT_STATUS_NAME,
862
841
  AWAITING_WORKSPACE_STATUS_NAME,
863
842
  PREPARATION_STATUS_NAME,
864
843
  FAILED_PREPARATION_STATUS_NAME,
@@ -942,12 +921,6 @@ describe('SetupTowerDefenceProjectUseCase', () => {
942
921
  const statuses: FieldOption[] = [
943
922
  {
944
923
  id: 'id-0',
945
- name: DEFAULT_STATUS_NAME,
946
- color: 'ORANGE',
947
- description: '',
948
- },
949
- {
950
- id: 'id-1',
951
924
  name: LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
952
925
  color: 'ORANGE',
953
926
  description: '',
@@ -1012,7 +985,6 @@ describe('SetupTowerDefenceProjectUseCase', () => {
1012
985
  const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
1013
986
 
1014
987
  expect(payload.map((s) => s.name)).toEqual([
1015
- DEFAULT_STATUS_NAME,
1016
988
  AWAITING_WORKSPACE_STATUS_NAME,
1017
989
  PREPARATION_STATUS_NAME,
1018
990
  FAILED_PREPARATION_STATUS_NAME,
@@ -1040,4 +1012,70 @@ describe('SetupTowerDefenceProjectUseCase', () => {
1040
1012
  ),
1041
1013
  ).toBe(false);
1042
1014
  });
1015
+
1016
+ it('should migrate issues from Unread to Awaiting Workspace when both statuses exist in the project', async () => {
1017
+ const mockProjectRepository =
1018
+ mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
1019
+ const mockIssueRepository =
1020
+ mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
1021
+ const awaitingWorkspaceId = 'aws-status-id';
1022
+ const statuses: FieldOption[] = [
1023
+ { id: 'unread-id', name: 'Unread', color: 'ORANGE', description: '' },
1024
+ {
1025
+ id: awaitingWorkspaceId,
1026
+ name: AWAITING_WORKSPACE_STATUS_NAME,
1027
+ color: 'BLUE',
1028
+ description: '',
1029
+ },
1030
+ ...buildCanonicalStatuses().filter(
1031
+ (s) => s.name !== AWAITING_WORKSPACE_STATUS_NAME,
1032
+ ),
1033
+ ];
1034
+ const project = buildProject(statuses);
1035
+ mockProjectRepository.getByUrl.mockResolvedValue(project);
1036
+ mockProjectRepository.updateStatusList.mockResolvedValue([]);
1037
+
1038
+ const unreadIssue1 = buildIssue({
1039
+ number: 1,
1040
+ url: 'https://github.com/test-org/test-repo/issues/1',
1041
+ itemId: 'item-1',
1042
+ status: 'Unread',
1043
+ });
1044
+ const unreadIssue2 = buildIssue({
1045
+ number: 2,
1046
+ url: 'https://github.com/test-org/test-repo/issues/2',
1047
+ itemId: 'item-2',
1048
+ status: 'Unread',
1049
+ });
1050
+ const awsIssue = buildIssue({
1051
+ number: 3,
1052
+ url: 'https://github.com/test-org/test-repo/issues/3',
1053
+ itemId: 'item-3',
1054
+ status: AWAITING_WORKSPACE_STATUS_NAME,
1055
+ });
1056
+ mockIssueRepository.getAllIssues.mockResolvedValue({
1057
+ project: mock<Project>(),
1058
+ issues: [unreadIssue1, unreadIssue2, awsIssue],
1059
+ cacheUsed: false,
1060
+ });
1061
+ mockIssueRepository.updateStatus.mockResolvedValue(undefined);
1062
+
1063
+ const useCase = new SetupTowerDefenceProjectUseCase(
1064
+ mockProjectRepository,
1065
+ mockIssueRepository,
1066
+ );
1067
+ await useCase.run({ projectUrl: project.url });
1068
+
1069
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledTimes(2);
1070
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1071
+ project,
1072
+ unreadIssue1,
1073
+ awaitingWorkspaceId,
1074
+ );
1075
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1076
+ project,
1077
+ unreadIssue2,
1078
+ awaitingWorkspaceId,
1079
+ );
1080
+ });
1043
1081
  });
@@ -2,7 +2,7 @@ import { FieldOption } from '../entities/Project';
2
2
  import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
3
3
  import { IssueRepository } from './adapter-interfaces/IssueRepository';
4
4
  import {
5
- DEFAULT_STATUS_NAME,
5
+ AWAITING_WORKSPACE_STATUS_NAME,
6
6
  IN_TMUX_STATUS_NAME,
7
7
  LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
8
8
  LEGACY_IN_TMUX_STATUS_NAME,
@@ -28,22 +28,50 @@ export class SetupTowerDefenceProjectUseCase {
28
28
  private static readonly LEGACY_STATUS_NAMES: Readonly<
29
29
  Record<string, string>
30
30
  > = {
31
- [DEFAULT_STATUS_NAME]: LEGACY_TODO_STATUS_NAME,
31
+ [AWAITING_WORKSPACE_STATUS_NAME]: LEGACY_TODO_STATUS_NAME,
32
32
  [TODO_STATUS_NAME]: LEGACY_TODO_STATUS_NAME,
33
33
  [IN_TMUX_STATUS_NAME]: LEGACY_IN_TMUX_STATUS_NAME,
34
34
  };
35
35
 
36
+ private static readonly UNREAD_MIGRATED_STATUS_NAME = 'Unread' as const;
37
+
36
38
  private static readonly MIGRATED_FROM_NAMES: ReadonlySet<string> = new Set([
37
39
  LEGACY_TODO_STATUS_NAME,
38
40
  LEGACY_IN_TMUX_STATUS_NAME,
39
41
  PC_TODO_STATUS_NAME,
40
42
  LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
43
+ SetupTowerDefenceProjectUseCase.UNREAD_MIGRATED_STATUS_NAME,
41
44
  ]);
42
45
 
43
46
  run = async (params: { projectUrl: string }): Promise<void> => {
44
47
  const project = await this.projectRepository.getByUrl(params.projectUrl);
45
48
  const existing = project.status.statuses;
46
49
 
50
+ const unreadStatus = existing.find(
51
+ (s) =>
52
+ s.name === SetupTowerDefenceProjectUseCase.UNREAD_MIGRATED_STATUS_NAME,
53
+ );
54
+ if (unreadStatus) {
55
+ const awaitingWorkspaceStatus = existing.find(
56
+ (s) => s.name === AWAITING_WORKSPACE_STATUS_NAME,
57
+ );
58
+ if (awaitingWorkspaceStatus) {
59
+ const { issues } = await this.issueRepository.getAllIssues(project.id);
60
+ const unreadIssues = issues.filter(
61
+ (issue) =>
62
+ issue.status ===
63
+ SetupTowerDefenceProjectUseCase.UNREAD_MIGRATED_STATUS_NAME,
64
+ );
65
+ for (const issue of unreadIssues) {
66
+ await this.issueRepository.updateStatus(
67
+ project,
68
+ issue,
69
+ awaitingWorkspaceStatus.id,
70
+ );
71
+ }
72
+ }
73
+ }
74
+
47
75
  const awaitingTaskBreakdownStatus = existing.find(
48
76
  (s) => s.name === LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
49
77
  );