github-issue-tower-defence-management 1.60.1 → 1.63.1
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/.github/workflows/publish.yml +13 -0
- package/.github/workflows/test.yml +0 -4
- package/CHANGELOG.md +14 -0
- package/README.md +53 -10
- package/bin/adapter/entry-points/cli/index.js +11 -11
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js +3 -22
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +8 -22
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/rotationOrderFileWriter.js +56 -0
- package/bin/adapter/entry-points/handlers/rotationOrderFileWriter.js.map +1 -0
- package/bin/adapter/entry-points/handlers/situationFileWriter.js +5 -0
- package/bin/adapter/entry-points/handlers/situationFileWriter.js.map +1 -1
- package/bin/adapter/proxy/TokenListLoader.js +21 -6
- package/bin/adapter/proxy/TokenListLoader.js.map +1 -1
- package/bin/adapter/proxy/proxyEntry.js +1 -0
- package/bin/adapter/proxy/proxyEntry.js.map +1 -1
- package/bin/adapter/repositories/BaseGitHubRepository.js +1 -113
- package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -1
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +5 -3
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +8 -7
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js +19 -9
- package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +15 -3
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js +8 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +5 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js +32 -1
- package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +91 -12
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -4
- package/src/adapter/entry-points/cli/index.test.ts +16 -16
- package/src/adapter/entry-points/cli/index.ts +8 -11
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +2 -55
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +1 -11
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +6 -56
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +7 -11
- package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +177 -0
- package/src/adapter/entry-points/handlers/rotationOrderFileWriter.ts +20 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +36 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.ts +8 -0
- package/src/adapter/proxy/TokenListLoader.test.ts +50 -1
- package/src/adapter/proxy/TokenListLoader.ts +25 -5
- package/src/adapter/proxy/proxyEntry.test.ts +270 -1
- package/src/adapter/proxy/proxyEntry.ts +2 -1
- package/src/adapter/repositories/BaseGitHubRepository.test.ts +1 -186
- package/src/adapter/repositories/BaseGitHubRepository.ts +1 -139
- package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +0 -1
- package/src/adapter/repositories/GraphqlProjectRepository.fetchProjectId.test.ts +4 -1
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +60 -19
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +6 -4
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +23 -13
- package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +0 -1
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +0 -8
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +0 -1
- package/src/domain/entities/ClaudeTokenUsage.ts +1 -0
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +196 -11
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +32 -15
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +4 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +21 -5
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +153 -0
- package/src/domain/usecases/IssueRejectionEvaluator.ts +8 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +175 -31
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +7 -1
- package/src/domain/usecases/RevertNotReadyAwaitingQualityCheckUseCase.test.ts +32 -0
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +39 -5
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +1 -1
- package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +139 -20
- package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +62 -2
- package/src/domain/usecases/StartPreparationUseCase.test.ts +404 -21
- package/src/domain/usecases/StartPreparationUseCase.ts +152 -16
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +16 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/rotationOrderFileWriter.d.ts +3 -0
- package/types/adapter/entry-points/handlers/rotationOrderFileWriter.d.ts.map +1 -0
- package/types/adapter/entry-points/handlers/situationFileWriter.d.ts +1 -0
- package/types/adapter/entry-points/handlers/situationFileWriter.d.ts.map +1 -1
- package/types/adapter/proxy/TokenListLoader.d.ts +5 -0
- package/types/adapter/proxy/TokenListLoader.d.ts.map +1 -1
- package/types/adapter/proxy/proxyEntry.d.ts +2 -1
- package/types/adapter/proxy/proxyEntry.d.ts.map +1 -1
- package/types/adapter/repositories/BaseGitHubRepository.d.ts +1 -23
- package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -1
- package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +14 -5
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/entities/ClaudeTokenUsage.d.ts +1 -0
- package/types/domain/entities/ClaudeTokenUsage.d.ts.map +1 -1
- package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts +4 -2
- package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +5 -2
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts +5 -2
- package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +15 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +14 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js +0 -136
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +0 -1
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +0 -1606
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +0 -1
- package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +0 -6552
- package/src/adapter/repositories/issue/CheerioIssueRepository.ts +0 -142
- package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.test.ts +0 -118
- package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +0 -584
- package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts +0 -40
- package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +0 -1
- package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts +0 -220
- package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +0 -1
|
@@ -26,27 +26,51 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
26
26
|
},
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const issueWithNewStoryLabel = {
|
|
29
|
+
const issueWithNewStoryLabel: Issue = {
|
|
30
30
|
...mock<Issue>(),
|
|
31
|
+
url: 'https://github.com/org/repo/issues/999',
|
|
31
32
|
title: 'New Feature Request',
|
|
32
33
|
number: 999,
|
|
34
|
+
story: 'Existing Story',
|
|
33
35
|
labels: ['bug', 'new-story', 'priority-high'],
|
|
34
36
|
};
|
|
35
37
|
|
|
36
|
-
const issueWithNewStoryLabelVariant = {
|
|
38
|
+
const issueWithNewStoryLabelVariant: Issue = {
|
|
37
39
|
...mock<Issue>(),
|
|
40
|
+
url: 'https://github.com/org/repo/issues/1000',
|
|
38
41
|
title: 'Another New Story',
|
|
39
42
|
number: 1000,
|
|
43
|
+
story: 'Existing Story',
|
|
40
44
|
labels: ['newstory', 'enhancement'],
|
|
41
45
|
};
|
|
42
46
|
|
|
43
|
-
const regularIssue = {
|
|
47
|
+
const regularIssue: Issue = {
|
|
44
48
|
...mock<Issue>(),
|
|
49
|
+
url: 'https://github.com/org/repo/issues/888',
|
|
45
50
|
title: 'Regular Issue',
|
|
46
51
|
number: 888,
|
|
52
|
+
story: 'Existing Story',
|
|
47
53
|
labels: ['bug', 'priority-low'],
|
|
48
54
|
};
|
|
49
55
|
|
|
56
|
+
const unassignedIssueWithNewStoryLabel: Issue = {
|
|
57
|
+
...mock<Issue>(),
|
|
58
|
+
url: 'https://github.com/org/repo/issues/1383',
|
|
59
|
+
title: 'Unassigned Story Issue',
|
|
60
|
+
number: 1383,
|
|
61
|
+
story: null,
|
|
62
|
+
labels: ['new-story'],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const unassignedRegularIssue: Issue = {
|
|
66
|
+
...mock<Issue>(),
|
|
67
|
+
url: 'https://github.com/org/repo/issues/1384',
|
|
68
|
+
title: 'Unassigned Regular Issue',
|
|
69
|
+
number: 1384,
|
|
70
|
+
story: null,
|
|
71
|
+
labels: ['bug'],
|
|
72
|
+
};
|
|
73
|
+
|
|
50
74
|
const storyObjectWithNewStoryIssues: StoryObject = {
|
|
51
75
|
story: {
|
|
52
76
|
...mock<StoryOption>(),
|
|
@@ -85,6 +109,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
85
109
|
org: 'testOrg',
|
|
86
110
|
repo: 'testRepo',
|
|
87
111
|
storyObjectMap: new Map([['Story 1', storyObjectWithNewStoryIssues]]),
|
|
112
|
+
issues: [],
|
|
88
113
|
});
|
|
89
114
|
|
|
90
115
|
expect(mockProjectRepository.updateStoryList).not.toHaveBeenCalled();
|
|
@@ -107,6 +132,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
107
132
|
storyObjectMap: new Map([
|
|
108
133
|
['Story 1', storyObjectWithoutNewStoryIssues],
|
|
109
134
|
]),
|
|
135
|
+
issues: [regularIssue, unassignedRegularIssue],
|
|
110
136
|
});
|
|
111
137
|
|
|
112
138
|
expect(mockProjectRepository.updateStoryList).not.toHaveBeenCalled();
|
|
@@ -141,6 +167,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
141
167
|
org: 'testOrg',
|
|
142
168
|
repo: 'testRepo',
|
|
143
169
|
storyObjectMap: new Map([['Story 1', storyObjectWithNewStoryIssues]]),
|
|
170
|
+
issues: [],
|
|
144
171
|
});
|
|
145
172
|
|
|
146
173
|
expect(mockProjectRepository.updateStoryList).toHaveBeenCalledTimes(1);
|
|
@@ -207,11 +234,84 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
207
234
|
);
|
|
208
235
|
});
|
|
209
236
|
|
|
237
|
+
it('should create new story for an unassigned issue with new-story label', async () => {
|
|
238
|
+
const savedStories: FieldOption[] = [
|
|
239
|
+
{ id: 'story1', name: 'First Story', color: 'BLUE', description: '' },
|
|
240
|
+
{
|
|
241
|
+
id: 'newStoryId1',
|
|
242
|
+
name: 'Unassigned Story Issue',
|
|
243
|
+
description: '',
|
|
244
|
+
color: 'RED',
|
|
245
|
+
},
|
|
246
|
+
{ id: 'story2', name: 'Middle Story', color: 'GREEN', description: '' },
|
|
247
|
+
{ id: 'story3', name: 'Last Story', color: 'YELLOW', description: '' },
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
mockProjectRepository.updateStoryList.mockResolvedValue(savedStories);
|
|
251
|
+
|
|
252
|
+
const emptyStoryObjectMap = new Map<string, StoryObject>();
|
|
253
|
+
|
|
254
|
+
await useCase.run({
|
|
255
|
+
project: basicProject,
|
|
256
|
+
cacheUsed: false,
|
|
257
|
+
org: 'testOrg',
|
|
258
|
+
repo: 'testRepo',
|
|
259
|
+
storyObjectMap: emptyStoryObjectMap,
|
|
260
|
+
issues: [unassignedIssueWithNewStoryLabel, unassignedRegularIssue],
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
expect(mockProjectRepository.updateStoryList).toHaveBeenCalledTimes(1);
|
|
264
|
+
expect(mockProjectRepository.updateStoryList).toHaveBeenCalledWith(
|
|
265
|
+
basicProject,
|
|
266
|
+
[
|
|
267
|
+
{
|
|
268
|
+
id: 'story1',
|
|
269
|
+
name: 'First Story',
|
|
270
|
+
color: 'BLUE',
|
|
271
|
+
description: '',
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: null,
|
|
275
|
+
name: 'Unassigned Story Issue',
|
|
276
|
+
description: '',
|
|
277
|
+
color: 'RED',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: 'story2',
|
|
281
|
+
name: 'Middle Story',
|
|
282
|
+
color: 'GREEN',
|
|
283
|
+
description: '',
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: 'story3',
|
|
287
|
+
name: 'Last Story',
|
|
288
|
+
color: 'YELLOW',
|
|
289
|
+
description: '',
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
expect(mockIssueRepository.updateStory).toHaveBeenCalledTimes(1);
|
|
295
|
+
expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
|
|
296
|
+
{ ...basicProject, story: basicProject.story },
|
|
297
|
+
unassignedIssueWithNewStoryLabel,
|
|
298
|
+
'newStoryId1',
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
expect(mockIssueRepository.updateLabels).toHaveBeenCalledTimes(1);
|
|
302
|
+
expect(mockIssueRepository.updateLabels).toHaveBeenCalledWith(
|
|
303
|
+
unassignedIssueWithNewStoryLabel,
|
|
304
|
+
[],
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
|
|
210
308
|
it('should skip issues when no matching story is found in saved list', async () => {
|
|
211
|
-
const issueWithUnmatchedTitle = {
|
|
309
|
+
const issueWithUnmatchedTitle: Issue = {
|
|
212
310
|
...mock<Issue>(),
|
|
311
|
+
url: 'https://github.com/org/repo/issues/1001',
|
|
213
312
|
title: 'Unmatched Title',
|
|
214
313
|
number: 1001,
|
|
314
|
+
story: 'Existing Story',
|
|
215
315
|
labels: ['new-story'],
|
|
216
316
|
};
|
|
217
317
|
|
|
@@ -237,6 +337,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
237
337
|
org: 'testOrg',
|
|
238
338
|
repo: 'testRepo',
|
|
239
339
|
storyObjectMap: new Map([['Story 1', storyObject]]),
|
|
340
|
+
issues: [],
|
|
240
341
|
});
|
|
241
342
|
|
|
242
343
|
expect(mockProjectRepository.updateStoryList).toHaveBeenCalledTimes(1);
|
|
@@ -246,24 +347,30 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
246
347
|
});
|
|
247
348
|
|
|
248
349
|
describe('findNewStoryIssues', () => {
|
|
249
|
-
const issueWithNoHyphen = {
|
|
350
|
+
const issueWithNoHyphen: Issue = {
|
|
250
351
|
...mock<Issue>(),
|
|
352
|
+
url: 'https://github.com/org/repo/issues/1002',
|
|
251
353
|
title: 'No Hyphen Issue',
|
|
252
354
|
number: 1002,
|
|
355
|
+
story: 'Some Story',
|
|
253
356
|
labels: ['newstory'],
|
|
254
357
|
};
|
|
255
358
|
|
|
256
|
-
const issueWithMixedCase = {
|
|
359
|
+
const issueWithMixedCase: Issue = {
|
|
257
360
|
...mock<Issue>(),
|
|
361
|
+
url: 'https://github.com/org/repo/issues/1003',
|
|
258
362
|
title: 'Mixed Case Issue',
|
|
259
363
|
number: 1003,
|
|
364
|
+
story: 'Some Story',
|
|
260
365
|
labels: ['NEW-STORY'],
|
|
261
366
|
};
|
|
262
367
|
|
|
263
|
-
const anotherIssueWithLabel = {
|
|
368
|
+
const anotherIssueWithLabel: Issue = {
|
|
264
369
|
...mock<Issue>(),
|
|
370
|
+
url: 'https://github.com/org/repo/issues/1004',
|
|
265
371
|
title: 'Another Issue',
|
|
266
372
|
number: 1004,
|
|
373
|
+
story: 'Some Story',
|
|
267
374
|
labels: ['newstory'],
|
|
268
375
|
};
|
|
269
376
|
|
|
@@ -294,6 +401,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
294
401
|
const testCases: Array<{
|
|
295
402
|
description: string;
|
|
296
403
|
storyObjectMap: Map<string, StoryObject>;
|
|
404
|
+
issues: Issue[];
|
|
297
405
|
expectedLength: number;
|
|
298
406
|
expectedIssues: Issue[];
|
|
299
407
|
}> = [
|
|
@@ -301,24 +409,28 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
301
409
|
description:
|
|
302
410
|
'should return empty array when no issues have new-story label',
|
|
303
411
|
storyObjectMap: new Map([['Story 1', storyObjectWithRegularIssue]]),
|
|
412
|
+
issues: [unassignedRegularIssue],
|
|
304
413
|
expectedLength: 0,
|
|
305
414
|
expectedIssues: [],
|
|
306
415
|
},
|
|
307
416
|
{
|
|
308
417
|
description: 'should find issues with "new-story" label (with hyphen)',
|
|
309
418
|
storyObjectMap: new Map([['Story 1', storyObjectWithNewStoryIssues]]),
|
|
419
|
+
issues: [],
|
|
310
420
|
expectedLength: 2,
|
|
311
421
|
expectedIssues: [issueWithNewStoryLabel, issueWithNewStoryLabelVariant],
|
|
312
422
|
},
|
|
313
423
|
{
|
|
314
424
|
description: 'should find issues with "newstory" label (no hyphen)',
|
|
315
425
|
storyObjectMap: new Map([['Story 1', storyObjectWithNoHyphen]]),
|
|
426
|
+
issues: [],
|
|
316
427
|
expectedLength: 1,
|
|
317
428
|
expectedIssues: [issueWithNoHyphen],
|
|
318
429
|
},
|
|
319
430
|
{
|
|
320
431
|
description: 'should find issues with mixed case "NEW-STORY" label',
|
|
321
432
|
storyObjectMap: new Map([['Story 1', storyObjectWithMixedCase]]),
|
|
433
|
+
issues: [],
|
|
322
434
|
expectedLength: 1,
|
|
323
435
|
expectedIssues: [issueWithMixedCase],
|
|
324
436
|
},
|
|
@@ -328,6 +440,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
328
440
|
['Story 1', storyObjectWithNewStoryIssues],
|
|
329
441
|
['Story 2', storyObjectWithAnotherIssue],
|
|
330
442
|
]),
|
|
443
|
+
issues: [],
|
|
331
444
|
expectedLength: 3,
|
|
332
445
|
expectedIssues: [
|
|
333
446
|
issueWithNewStoryLabel,
|
|
@@ -335,12 +448,32 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
335
448
|
anotherIssueWithLabel,
|
|
336
449
|
],
|
|
337
450
|
},
|
|
451
|
+
{
|
|
452
|
+
description:
|
|
453
|
+
'should find unassigned issues with new-story label not present in storyObjectMap',
|
|
454
|
+
storyObjectMap: new Map<string, StoryObject>(),
|
|
455
|
+
issues: [unassignedIssueWithNewStoryLabel, unassignedRegularIssue],
|
|
456
|
+
expectedLength: 1,
|
|
457
|
+
expectedIssues: [unassignedIssueWithNewStoryLabel],
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
description:
|
|
461
|
+
'should not duplicate issue that appears in both storyObjectMap and issues array',
|
|
462
|
+
storyObjectMap: new Map([['Story 1', storyObjectWithNewStoryIssues]]),
|
|
463
|
+
issues: [issueWithNewStoryLabel, unassignedIssueWithNewStoryLabel],
|
|
464
|
+
expectedLength: 3,
|
|
465
|
+
expectedIssues: [
|
|
466
|
+
issueWithNewStoryLabel,
|
|
467
|
+
issueWithNewStoryLabelVariant,
|
|
468
|
+
unassignedIssueWithNewStoryLabel,
|
|
469
|
+
],
|
|
470
|
+
},
|
|
338
471
|
];
|
|
339
472
|
|
|
340
473
|
test.each(testCases)(
|
|
341
474
|
'$description',
|
|
342
|
-
({ storyObjectMap, expectedLength, expectedIssues }) => {
|
|
343
|
-
const result = useCase.findNewStoryIssues(storyObjectMap);
|
|
475
|
+
({ storyObjectMap, issues, expectedLength, expectedIssues }) => {
|
|
476
|
+
const result = useCase.findNewStoryIssues(storyObjectMap, issues);
|
|
344
477
|
|
|
345
478
|
expect(result).toHaveLength(expectedLength);
|
|
346
479
|
expectedIssues.forEach((issue) => {
|
|
@@ -367,6 +500,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
367
500
|
description: string;
|
|
368
501
|
projectStory: NonNullable<Project['story']>;
|
|
369
502
|
storyObjectMap: Map<string, StoryObject>;
|
|
503
|
+
issues: Issue[];
|
|
370
504
|
expected: (Omit<FieldOption, 'id'> & { id: FieldOption['id'] | null })[];
|
|
371
505
|
}> = [
|
|
372
506
|
{
|
|
@@ -399,6 +533,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
399
533
|
workflowManagementStory: { id: 'workflow1', name: 'Workflow Story' },
|
|
400
534
|
},
|
|
401
535
|
storyObjectMap: new Map([['Story 1', storyObjectWithSingleNewStory]]),
|
|
536
|
+
issues: [],
|
|
402
537
|
expected: [
|
|
403
538
|
{ id: 'story1', name: 'First Story', color: 'BLUE', description: '' },
|
|
404
539
|
{
|
|
@@ -450,6 +585,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
450
585
|
workflowManagementStory: { id: 'workflow1', name: 'Workflow Story' },
|
|
451
586
|
},
|
|
452
587
|
storyObjectMap: new Map([['Story 1', storyObjectWithNewStoryIssues]]),
|
|
588
|
+
issues: [],
|
|
453
589
|
expected: [
|
|
454
590
|
{ id: 'story1', name: 'First Story', color: 'BLUE', description: '' },
|
|
455
591
|
{
|
|
@@ -488,6 +624,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
488
624
|
workflowManagementStory: { id: 'workflow1', name: 'Workflow Story' },
|
|
489
625
|
},
|
|
490
626
|
storyObjectMap: new Map([['Story 1', storyObjectWithSingleNewStory]]),
|
|
627
|
+
issues: [],
|
|
491
628
|
expected: [
|
|
492
629
|
{
|
|
493
630
|
id: null,
|
|
@@ -514,6 +651,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
514
651
|
workflowManagementStory: { id: 'workflow1', name: 'Workflow Story' },
|
|
515
652
|
},
|
|
516
653
|
storyObjectMap: new Map([['Story 1', storyObjectWithSingleNewStory]]),
|
|
654
|
+
issues: [],
|
|
517
655
|
expected: [
|
|
518
656
|
{ id: 'story1', name: 'Only Story', color: 'BLUE', description: '' },
|
|
519
657
|
{
|
|
@@ -547,6 +685,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
547
685
|
workflowManagementStory: { id: 'workflow1', name: 'Workflow Story' },
|
|
548
686
|
},
|
|
549
687
|
storyObjectMap: new Map([['Story 1', storyObjectWithSingleNewStory]]),
|
|
688
|
+
issues: [],
|
|
550
689
|
expected: [
|
|
551
690
|
{ id: 'story1', name: 'First Story', color: 'BLUE', description: '' },
|
|
552
691
|
{
|
|
@@ -592,6 +731,7 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
592
731
|
workflowManagementStory: { id: 'workflow1', name: 'Workflow Story' },
|
|
593
732
|
},
|
|
594
733
|
storyObjectMap: new Map([['Story 1', storyObjectWithRegularIssueOnly]]),
|
|
734
|
+
issues: [unassignedRegularIssue],
|
|
595
735
|
expected: [
|
|
596
736
|
{ id: 'story1', name: 'First Story', color: 'BLUE', description: '' },
|
|
597
737
|
{
|
|
@@ -608,12 +748,57 @@ describe('CreateNewStoryByLabelUseCase', () => {
|
|
|
608
748
|
},
|
|
609
749
|
],
|
|
610
750
|
},
|
|
751
|
+
{
|
|
752
|
+
description:
|
|
753
|
+
'should include unassigned issue with new-story label in new story list',
|
|
754
|
+
projectStory: {
|
|
755
|
+
name: 'Story Field',
|
|
756
|
+
fieldId: 'storyFieldId',
|
|
757
|
+
databaseId: 123,
|
|
758
|
+
stories: [
|
|
759
|
+
{
|
|
760
|
+
id: 'story1',
|
|
761
|
+
name: 'First Story',
|
|
762
|
+
color: 'BLUE',
|
|
763
|
+
description: '',
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
id: 'story2',
|
|
767
|
+
name: 'Middle Story',
|
|
768
|
+
color: 'GREEN',
|
|
769
|
+
description: '',
|
|
770
|
+
},
|
|
771
|
+
],
|
|
772
|
+
workflowManagementStory: { id: 'workflow1', name: 'Workflow Story' },
|
|
773
|
+
},
|
|
774
|
+
storyObjectMap: new Map<string, StoryObject>(),
|
|
775
|
+
issues: [unassignedIssueWithNewStoryLabel],
|
|
776
|
+
expected: [
|
|
777
|
+
{ id: 'story1', name: 'First Story', color: 'BLUE', description: '' },
|
|
778
|
+
{
|
|
779
|
+
id: null,
|
|
780
|
+
name: 'Unassigned Story Issue',
|
|
781
|
+
color: 'RED',
|
|
782
|
+
description: '',
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
id: 'story2',
|
|
786
|
+
name: 'Middle Story',
|
|
787
|
+
color: 'GREEN',
|
|
788
|
+
description: '',
|
|
789
|
+
},
|
|
790
|
+
],
|
|
791
|
+
},
|
|
611
792
|
];
|
|
612
793
|
|
|
613
794
|
test.each(testCases)(
|
|
614
795
|
'$description',
|
|
615
|
-
({ projectStory, storyObjectMap, expected }) => {
|
|
616
|
-
const result = useCase.createNewStoryList(
|
|
796
|
+
({ projectStory, storyObjectMap, issues, expected }) => {
|
|
797
|
+
const result = useCase.createNewStoryList(
|
|
798
|
+
projectStory,
|
|
799
|
+
storyObjectMap,
|
|
800
|
+
issues,
|
|
801
|
+
);
|
|
617
802
|
|
|
618
803
|
expect(result).toEqual(expected);
|
|
619
804
|
},
|
|
@@ -19,18 +19,23 @@ export class CreateNewStoryByLabelUseCase {
|
|
|
19
19
|
org: string;
|
|
20
20
|
repo: string;
|
|
21
21
|
storyObjectMap: StoryObjectMap;
|
|
22
|
+
issues: Issue[];
|
|
22
23
|
}): Promise<void> => {
|
|
23
24
|
const projectStory = input.project.story;
|
|
24
25
|
if (!projectStory) {
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
27
|
-
const newStoryIssues = this.findNewStoryIssues(
|
|
28
|
+
const newStoryIssues = this.findNewStoryIssues(
|
|
29
|
+
input.storyObjectMap,
|
|
30
|
+
input.issues,
|
|
31
|
+
);
|
|
28
32
|
if (newStoryIssues.length === 0) {
|
|
29
33
|
return;
|
|
30
34
|
}
|
|
31
35
|
const newStoryList = this.createNewStoryList(
|
|
32
36
|
projectStory,
|
|
33
37
|
input.storyObjectMap,
|
|
38
|
+
input.issues,
|
|
34
39
|
);
|
|
35
40
|
const savedNewStoryList = await this.projectRepository.updateStoryList(
|
|
36
41
|
input.project,
|
|
@@ -55,26 +60,38 @@ export class CreateNewStoryByLabelUseCase {
|
|
|
55
60
|
);
|
|
56
61
|
}
|
|
57
62
|
};
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
|
|
64
|
+
hasNewStoryLabel = (issue: Issue): boolean =>
|
|
65
|
+
issue.labels?.some(
|
|
66
|
+
(label) => label.toLowerCase().replace('-', '') === 'newstory',
|
|
67
|
+
) ?? false;
|
|
68
|
+
|
|
69
|
+
findNewStoryIssues = (
|
|
70
|
+
storyObjectMap: StoryObjectMap,
|
|
71
|
+
issues: Issue[],
|
|
72
|
+
): Issue[] => {
|
|
73
|
+
const issuesInMap = Array.from(storyObjectMap.values())
|
|
60
74
|
.flatMap((storyObject) => storyObject.issues)
|
|
61
|
-
.filter(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
.filter(this.hasNewStoryLabel);
|
|
76
|
+
const unassignedIssuesWithLabel = issues
|
|
77
|
+
.filter((issue) => issue.story === null)
|
|
78
|
+
.filter(this.hasNewStoryLabel);
|
|
79
|
+
const seen = new Set<string>();
|
|
80
|
+
return [...issuesInMap, ...unassignedIssuesWithLabel].filter((issue) => {
|
|
81
|
+
if (seen.has(issue.url)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
seen.add(issue.url);
|
|
85
|
+
return true;
|
|
86
|
+
});
|
|
66
87
|
};
|
|
88
|
+
|
|
67
89
|
createNewStoryList = (
|
|
68
90
|
projectStory: NonNullable<Project['story']>,
|
|
69
91
|
storyObjectMap: StoryObjectMap,
|
|
92
|
+
issues: Issue[],
|
|
70
93
|
): (Omit<FieldOption, 'id'> & { id: FieldOption['id'] | null })[] => {
|
|
71
|
-
const newStoryIssues =
|
|
72
|
-
.flatMap((storyObject) => storyObject.issues)
|
|
73
|
-
.filter((issue) =>
|
|
74
|
-
issue.labels?.some(
|
|
75
|
-
(label) => label.toLowerCase().replace('-', '') === 'newstory',
|
|
76
|
-
),
|
|
77
|
-
);
|
|
94
|
+
const newStoryIssues = this.findNewStoryIssues(storyObjectMap, issues);
|
|
78
95
|
const newStoryList: (Omit<FieldOption, 'id'> & {
|
|
79
96
|
id: FieldOption['id'] | null;
|
|
80
97
|
})[] = [];
|
|
@@ -159,6 +159,9 @@ describe('HandleScheduledEventUseCase', () => {
|
|
|
159
159
|
['LastExecutionDateTime'],
|
|
160
160
|
['2024-01-01T00:00:00Z'],
|
|
161
161
|
]);
|
|
162
|
+
mockStartPreparationUseCase.run.mockResolvedValue({
|
|
163
|
+
rotationOrder: null,
|
|
164
|
+
});
|
|
162
165
|
});
|
|
163
166
|
|
|
164
167
|
it('should call AnalyzeProblemByIssueUseCase with correct parameters', async () => {
|
|
@@ -377,6 +380,7 @@ describe('HandleScheduledEventUseCase', () => {
|
|
|
377
380
|
});
|
|
378
381
|
mockStartPreparationUseCase.run.mockImplementation(async () => {
|
|
379
382
|
callOrder.push('startPreparation');
|
|
383
|
+
return { rotationOrder: null };
|
|
380
384
|
});
|
|
381
385
|
|
|
382
386
|
const input = {
|
|
@@ -19,7 +19,10 @@ import { SetNoStoryIssueToStoryUseCase } from './SetNoStoryIssueToStoryUseCase';
|
|
|
19
19
|
import { CreateNewStoryByLabelUseCase } from './CreateNewStoryByLabelUseCase';
|
|
20
20
|
import { AssignNoAssigneeIssueToManagerUseCase } from './AssignNoAssigneeIssueToManagerUseCase';
|
|
21
21
|
import { UpdateIssueStatusByLabelUseCase } from './UpdateIssueStatusByLabelUseCase';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
RotationOrderEntry,
|
|
24
|
+
StartPreparationUseCase,
|
|
25
|
+
} from './StartPreparationUseCase';
|
|
23
26
|
import { RevertOrphanedPreparationUseCase } from './RevertOrphanedPreparationUseCase';
|
|
24
27
|
import { RevertNotReadyAwaitingQualityCheckUseCase } from './RevertNotReadyAwaitingQualityCheckUseCase';
|
|
25
28
|
import { SetupTowerDefenceProjectUseCase } from './SetupTowerDefenceProjectUseCase';
|
|
@@ -96,6 +99,7 @@ export class HandleScheduledEventUseCase {
|
|
|
96
99
|
cacheUsed: boolean;
|
|
97
100
|
targetDateTimes: Date[];
|
|
98
101
|
storyIssues: StoryObjectMap;
|
|
102
|
+
rotationOrder: RotationOrderEntry[] | null;
|
|
99
103
|
} | null> => {
|
|
100
104
|
if (input.disabled) {
|
|
101
105
|
return null;
|
|
@@ -205,8 +209,9 @@ export class HandleScheduledEventUseCase {
|
|
|
205
209
|
now,
|
|
206
210
|
);
|
|
207
211
|
|
|
212
|
+
let rotationOrder: RotationOrderEntry[] | null = null;
|
|
208
213
|
try {
|
|
209
|
-
await this.runEachUseCases(
|
|
214
|
+
const useCaseResult = await this.runEachUseCases(
|
|
210
215
|
input,
|
|
211
216
|
project,
|
|
212
217
|
issues,
|
|
@@ -215,6 +220,7 @@ export class HandleScheduledEventUseCase {
|
|
|
215
220
|
storyIssues,
|
|
216
221
|
runSlowSweep,
|
|
217
222
|
);
|
|
223
|
+
rotationOrder = useCaseResult.rotationOrder;
|
|
218
224
|
} catch (e) {
|
|
219
225
|
if (!(e instanceof Error)) {
|
|
220
226
|
throw e;
|
|
@@ -238,7 +244,14 @@ ${JSON.stringify(e)}
|
|
|
238
244
|
throw e;
|
|
239
245
|
}
|
|
240
246
|
|
|
241
|
-
return {
|
|
247
|
+
return {
|
|
248
|
+
project,
|
|
249
|
+
issues,
|
|
250
|
+
cacheUsed,
|
|
251
|
+
targetDateTimes,
|
|
252
|
+
storyIssues,
|
|
253
|
+
rotationOrder,
|
|
254
|
+
};
|
|
242
255
|
};
|
|
243
256
|
runEachUseCases = async (
|
|
244
257
|
input: Parameters<HandleScheduledEventUseCase['run']>[0],
|
|
@@ -248,7 +261,7 @@ ${JSON.stringify(e)}
|
|
|
248
261
|
targetDateTimes: Date[],
|
|
249
262
|
storyObjectMap: StoryObjectMap,
|
|
250
263
|
runSlowSweep: boolean,
|
|
251
|
-
): Promise<
|
|
264
|
+
): Promise<{ rotationOrder: RotationOrderEntry[] | null }> => {
|
|
252
265
|
if (runSlowSweep) {
|
|
253
266
|
await this.runSlowSweepUseCases(
|
|
254
267
|
input,
|
|
@@ -284,7 +297,7 @@ ${JSON.stringify(e)}
|
|
|
284
297
|
input.startPreparation.awaitingQualityCheckStatus ?? undefined,
|
|
285
298
|
});
|
|
286
299
|
}
|
|
287
|
-
await this.startPreparationUseCase.run({
|
|
300
|
+
const preparationResult = await this.startPreparationUseCase.run({
|
|
288
301
|
projectUrl: input.projectUrl,
|
|
289
302
|
defaultAgentName: input.startPreparation.defaultAgentName,
|
|
290
303
|
defaultLlmModelName: input.startPreparation.defaultLlmModelName ?? null,
|
|
@@ -298,7 +311,9 @@ ${JSON.stringify(e)}
|
|
|
298
311
|
codexHomeCandidates: input.startPreparation.codexHomeCandidates ?? null,
|
|
299
312
|
allowIssueCacheMinutes: input.allowIssueCacheMinutes,
|
|
300
313
|
});
|
|
314
|
+
return { rotationOrder: preparationResult.rotationOrder };
|
|
301
315
|
}
|
|
316
|
+
return { rotationOrder: null };
|
|
302
317
|
};
|
|
303
318
|
runSlowSweepUseCases = async (
|
|
304
319
|
input: Parameters<HandleScheduledEventUseCase['run']>[0],
|
|
@@ -388,6 +403,7 @@ ${JSON.stringify(e)}
|
|
|
388
403
|
org: input.org,
|
|
389
404
|
repo: input.workingReport.repo,
|
|
390
405
|
storyObjectMap: storyObjectMap,
|
|
406
|
+
issues: issues,
|
|
391
407
|
});
|
|
392
408
|
await this.assignNoAssigneeIssueToManagerUseCase.run({
|
|
393
409
|
issues,
|