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
|
@@ -83,6 +83,7 @@ const createPassingPr = () => ({
|
|
|
83
83
|
url: 'https://github.com/user/repo/pull/5',
|
|
84
84
|
branchName: 'i1',
|
|
85
85
|
createdAt: new Date('2024-01-01T00:00:00Z'),
|
|
86
|
+
isDraft: false,
|
|
86
87
|
isConflicted: false,
|
|
87
88
|
isPassedAllCiJob: true,
|
|
88
89
|
isCiStateSuccess: true,
|
|
@@ -193,7 +194,7 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
193
194
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
194
195
|
{
|
|
195
196
|
author: 'bot',
|
|
196
|
-
content: 'From: agent report',
|
|
197
|
+
content: 'From: :robot: agent report',
|
|
197
198
|
createdAt: new Date(),
|
|
198
199
|
},
|
|
199
200
|
]);
|
|
@@ -228,7 +229,7 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
228
229
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
229
230
|
{
|
|
230
231
|
author: 'bot',
|
|
231
|
-
content: 'From: agent report',
|
|
232
|
+
content: 'From: :robot: agent report',
|
|
232
233
|
createdAt: new Date(),
|
|
233
234
|
},
|
|
234
235
|
]);
|
|
@@ -266,7 +267,7 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
266
267
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
267
268
|
{
|
|
268
269
|
author: 'bot',
|
|
269
|
-
content: 'From: agent report',
|
|
270
|
+
content: 'From: :robot: agent report',
|
|
270
271
|
createdAt: new Date(),
|
|
271
272
|
},
|
|
272
273
|
]);
|
|
@@ -300,7 +301,7 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
300
301
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
301
302
|
{
|
|
302
303
|
author: 'bot',
|
|
303
|
-
content: 'From: agent report',
|
|
304
|
+
content: 'From: :robot: agent report',
|
|
304
305
|
createdAt: new Date(),
|
|
305
306
|
},
|
|
306
307
|
]);
|
|
@@ -334,7 +335,40 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
334
335
|
{
|
|
335
336
|
author: 'bot',
|
|
336
337
|
content:
|
|
337
|
-
'From: agent report\n```json\n{"nextStep": "do something"}\n```',
|
|
338
|
+
'From: :robot: agent report\n```json\n{"nextStep": "do something"}\n```',
|
|
339
|
+
createdAt: new Date(),
|
|
340
|
+
},
|
|
341
|
+
]);
|
|
342
|
+
|
|
343
|
+
await useCase.run({
|
|
344
|
+
projectUrl: 'https://github.com/user/repo',
|
|
345
|
+
allowIssueCacheMinutes: 60,
|
|
346
|
+
preparationProcessCheckCommand: 'pgrep -fa "claude-agent.*{URL}"',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
350
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][2]).toBe('1');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should revert orphaned issue to Awaiting Workspace when last comment is a cross-issue notification starting with From: :warning:', async () => {
|
|
354
|
+
const stuckIssue = createMockIssue({
|
|
355
|
+
url: 'https://github.com/user/repo/issues/10',
|
|
356
|
+
status: 'Preparation',
|
|
357
|
+
});
|
|
358
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
359
|
+
issues: [stuckIssue],
|
|
360
|
+
cacheUsed: false,
|
|
361
|
+
});
|
|
362
|
+
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
363
|
+
stdout: '',
|
|
364
|
+
stderr: '',
|
|
365
|
+
exitCode: 1,
|
|
366
|
+
});
|
|
367
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
368
|
+
{
|
|
369
|
+
author: 'bot',
|
|
370
|
+
content:
|
|
371
|
+
'From: :warning: This message is from https://github.com/user/repo/tree/i999 AI HS Implement AI Agent (claude-sonnet-4-6)',
|
|
338
372
|
createdAt: new Date(),
|
|
339
373
|
},
|
|
340
374
|
]);
|
|
@@ -102,7 +102,7 @@ export class RevertOrphanedPreparationUseCase {
|
|
|
102
102
|
const comments =
|
|
103
103
|
await this.issueCommentRepository.getCommentsFromIssue(issue);
|
|
104
104
|
const lastComment = comments[comments.length - 1];
|
|
105
|
-
if (!lastComment || !lastComment.content.startsWith('From:')) {
|
|
105
|
+
if (!lastComment || !lastComment.content.startsWith('From: :robot:')) {
|
|
106
106
|
return true;
|
|
107
107
|
}
|
|
108
108
|
if (this.reportBodyHasNextStep(lastComment.content)) {
|
|
@@ -353,7 +353,7 @@ describe('SetWorkflowManagementIssueToStoryUseCase', () => {
|
|
|
353
353
|
]);
|
|
354
354
|
});
|
|
355
355
|
|
|
356
|
-
it('should
|
|
356
|
+
it('should skip issue and create one notification issue when story:* label has no matching regular / ... option', async () => {
|
|
357
357
|
const issue: Issue = {
|
|
358
358
|
...mock<Issue>(),
|
|
359
359
|
labels: ['story:routine-management'],
|
|
@@ -362,24 +362,88 @@ describe('SetWorkflowManagementIssueToStoryUseCase', () => {
|
|
|
362
362
|
nextActionDate: null,
|
|
363
363
|
nextActionHour: null,
|
|
364
364
|
isPr: false,
|
|
365
|
+
org: 'xcare-medical',
|
|
366
|
+
repo: 'xcare-platform',
|
|
367
|
+
url: 'https://github.com/xcare-medical/xcare-platform/issues/1445',
|
|
365
368
|
};
|
|
369
|
+
mockIssueRepository.searchIssue.mockResolvedValue([]);
|
|
366
370
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
371
|
+
const promise = useCase.run({
|
|
372
|
+
targetDates: [targetDate],
|
|
373
|
+
project: basicProject,
|
|
374
|
+
issues: [issue],
|
|
375
|
+
cacheUsed: false,
|
|
376
|
+
});
|
|
377
|
+
await jest.runAllTimersAsync();
|
|
378
|
+
await promise;
|
|
379
|
+
|
|
380
|
+
expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
|
|
381
|
+
expect(mockIssueRepository.removeLabel).not.toHaveBeenCalled();
|
|
382
|
+
expect(mockIssueRepository.searchIssue.mock.calls).toEqual([
|
|
383
|
+
[
|
|
384
|
+
{
|
|
385
|
+
owner: 'xcare-medical',
|
|
386
|
+
repositoryName: 'xcare-platform',
|
|
387
|
+
type: 'issue',
|
|
388
|
+
state: 'open',
|
|
389
|
+
title:
|
|
390
|
+
'TDPM: story label "story:routine-management" has no matching "regular / routine-management" Story option',
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
]);
|
|
394
|
+
expect(mockIssueRepository.createNewIssue).toHaveBeenCalledTimes(1);
|
|
395
|
+
const createCall = mockIssueRepository.createNewIssue.mock.calls[0];
|
|
396
|
+
expect(createCall[0]).toEqual('xcare-medical');
|
|
397
|
+
expect(createCall[1]).toEqual('xcare-platform');
|
|
398
|
+
expect(createCall[2]).toEqual(
|
|
399
|
+
'TDPM: story label "story:routine-management" has no matching "regular / routine-management" Story option',
|
|
400
|
+
);
|
|
401
|
+
expect(createCall[3]).toContain(
|
|
402
|
+
'https://github.com/xcare-medical/xcare-platform/issues/1445',
|
|
376
403
|
);
|
|
404
|
+
expect(createCall[3]).toContain('story:routine-management');
|
|
405
|
+
expect(createCall[4]).toEqual(['xcare-medical']);
|
|
406
|
+
expect(createCall[5]).toEqual([]);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('should not create a duplicate notification issue when an open one already exists', async () => {
|
|
410
|
+
const issue: Issue = {
|
|
411
|
+
...mock<Issue>(),
|
|
412
|
+
labels: ['story:routine-management'],
|
|
413
|
+
story: null,
|
|
414
|
+
state: 'OPEN',
|
|
415
|
+
nextActionDate: null,
|
|
416
|
+
nextActionHour: null,
|
|
417
|
+
isPr: false,
|
|
418
|
+
org: 'xcare-medical',
|
|
419
|
+
repo: 'xcare-platform',
|
|
420
|
+
url: 'https://github.com/xcare-medical/xcare-platform/issues/1445',
|
|
421
|
+
};
|
|
422
|
+
mockIssueRepository.searchIssue.mockResolvedValue([
|
|
423
|
+
{
|
|
424
|
+
url: 'https://github.com/xcare-medical/xcare-platform/issues/9999',
|
|
425
|
+
title:
|
|
426
|
+
'TDPM: story label "story:routine-management" has no matching "regular / routine-management" Story option',
|
|
427
|
+
number: '9999',
|
|
428
|
+
},
|
|
429
|
+
]);
|
|
430
|
+
|
|
431
|
+
const promise = useCase.run({
|
|
432
|
+
targetDates: [targetDate],
|
|
433
|
+
project: basicProject,
|
|
434
|
+
issues: [issue],
|
|
435
|
+
cacheUsed: false,
|
|
436
|
+
});
|
|
437
|
+
await jest.runAllTimersAsync();
|
|
438
|
+
await promise;
|
|
377
439
|
|
|
378
440
|
expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
|
|
379
441
|
expect(mockIssueRepository.removeLabel).not.toHaveBeenCalled();
|
|
442
|
+
expect(mockIssueRepository.searchIssue).toHaveBeenCalledTimes(1);
|
|
443
|
+
expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
|
|
380
444
|
});
|
|
381
445
|
|
|
382
|
-
it('should
|
|
446
|
+
it('should skip issue and notify when story option does not start with regular / ', async () => {
|
|
383
447
|
const issue: Issue = {
|
|
384
448
|
...mock<Issue>(),
|
|
385
449
|
labels: ['story:workflow-board'],
|
|
@@ -388,20 +452,75 @@ describe('SetWorkflowManagementIssueToStoryUseCase', () => {
|
|
|
388
452
|
nextActionDate: null,
|
|
389
453
|
nextActionHour: null,
|
|
390
454
|
isPr: false,
|
|
455
|
+
org: 'xcare-medical',
|
|
456
|
+
repo: 'xcare-platform',
|
|
457
|
+
url: 'https://github.com/xcare-medical/xcare-platform/issues/1500',
|
|
391
458
|
};
|
|
459
|
+
mockIssueRepository.searchIssue.mockResolvedValue([]);
|
|
460
|
+
|
|
461
|
+
const promise = useCase.run({
|
|
462
|
+
targetDates: [targetDate],
|
|
463
|
+
project: basicProject,
|
|
464
|
+
issues: [issue],
|
|
465
|
+
cacheUsed: false,
|
|
466
|
+
});
|
|
467
|
+
await jest.runAllTimersAsync();
|
|
468
|
+
await promise;
|
|
392
469
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
cacheUsed: false,
|
|
399
|
-
}),
|
|
400
|
-
).rejects.toThrow(
|
|
401
|
-
'No matching story found for label: story:workflow-board',
|
|
470
|
+
expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
|
|
471
|
+
expect(mockIssueRepository.removeLabel).not.toHaveBeenCalled();
|
|
472
|
+
expect(mockIssueRepository.createNewIssue).toHaveBeenCalledTimes(1);
|
|
473
|
+
expect(mockIssueRepository.createNewIssue.mock.calls[0][2]).toEqual(
|
|
474
|
+
'TDPM: story label "story:workflow-board" has no matching "regular / workflow-board" Story option',
|
|
402
475
|
);
|
|
403
476
|
});
|
|
404
477
|
|
|
478
|
+
it('should continue processing remaining issues after an unmatched label', async () => {
|
|
479
|
+
const unmatchedIssue: Issue = {
|
|
480
|
+
...mock<Issue>(),
|
|
481
|
+
labels: ['story:routine-management'],
|
|
482
|
+
story: null,
|
|
483
|
+
state: 'OPEN',
|
|
484
|
+
nextActionDate: null,
|
|
485
|
+
nextActionHour: null,
|
|
486
|
+
isPr: false,
|
|
487
|
+
org: 'xcare-medical',
|
|
488
|
+
repo: 'xcare-platform',
|
|
489
|
+
url: 'https://github.com/xcare-medical/xcare-platform/issues/1445',
|
|
490
|
+
};
|
|
491
|
+
const matchedIssue: Issue = {
|
|
492
|
+
...mock<Issue>(),
|
|
493
|
+
labels: ['story:high-priority'],
|
|
494
|
+
story: null,
|
|
495
|
+
state: 'OPEN',
|
|
496
|
+
nextActionDate: null,
|
|
497
|
+
nextActionHour: null,
|
|
498
|
+
isPr: false,
|
|
499
|
+
};
|
|
500
|
+
mockIssueRepository.searchIssue.mockResolvedValue([]);
|
|
501
|
+
|
|
502
|
+
const promise = useCase.run({
|
|
503
|
+
targetDates: [targetDate],
|
|
504
|
+
project: basicProject,
|
|
505
|
+
issues: [unmatchedIssue, matchedIssue],
|
|
506
|
+
cacheUsed: false,
|
|
507
|
+
});
|
|
508
|
+
await jest.runAllTimersAsync();
|
|
509
|
+
await promise;
|
|
510
|
+
|
|
511
|
+
expect(mockIssueRepository.createNewIssue).toHaveBeenCalledTimes(1);
|
|
512
|
+
expect(mockIssueRepository.updateStory.mock.calls).toEqual([
|
|
513
|
+
[
|
|
514
|
+
{ ...basicProject, story: basicProject.story },
|
|
515
|
+
matchedIssue,
|
|
516
|
+
'highPriorityId',
|
|
517
|
+
],
|
|
518
|
+
]);
|
|
519
|
+
expect(mockIssueRepository.removeLabel.mock.calls).toEqual([
|
|
520
|
+
[matchedIssue, 'story:high-priority'],
|
|
521
|
+
]);
|
|
522
|
+
});
|
|
523
|
+
|
|
405
524
|
it('should skip issue that already has a story assigned', async () => {
|
|
406
525
|
const issue: Issue = {
|
|
407
526
|
...mock<Issue>(),
|
|
@@ -6,7 +6,7 @@ export class SetWorkflowManagementIssueToStoryUseCase {
|
|
|
6
6
|
constructor(
|
|
7
7
|
readonly issueRepository: Pick<
|
|
8
8
|
IssueRepository,
|
|
9
|
-
'updateStory' | 'removeLabel'
|
|
9
|
+
'updateStory' | 'removeLabel' | 'searchIssue' | 'createNewIssue'
|
|
10
10
|
>,
|
|
11
11
|
) {}
|
|
12
12
|
|
|
@@ -103,7 +103,8 @@ export class SetWorkflowManagementIssueToStoryUseCase {
|
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
if (!matchingStory) {
|
|
106
|
-
|
|
106
|
+
await this.notifyUnmatchedStoryLabel(issue, storyLabel, labelSuffix);
|
|
107
|
+
continue;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
await this.issueRepository.updateStory(
|
|
@@ -116,6 +117,65 @@ export class SetWorkflowManagementIssueToStoryUseCase {
|
|
|
116
117
|
}
|
|
117
118
|
};
|
|
118
119
|
|
|
120
|
+
static buildUnmatchedStoryLabelTitle = (
|
|
121
|
+
storyLabel: string,
|
|
122
|
+
labelSuffix: string,
|
|
123
|
+
): string =>
|
|
124
|
+
`TDPM: story label "${storyLabel}" has no matching "${SetWorkflowManagementIssueToStoryUseCase.REGULAR_STORY_PREFIX}${labelSuffix}" Story option`;
|
|
125
|
+
|
|
126
|
+
private notifyUnmatchedStoryLabel = async (
|
|
127
|
+
issue: Issue,
|
|
128
|
+
storyLabel: string,
|
|
129
|
+
labelSuffix: string,
|
|
130
|
+
): Promise<void> => {
|
|
131
|
+
const title =
|
|
132
|
+
SetWorkflowManagementIssueToStoryUseCase.buildUnmatchedStoryLabelTitle(
|
|
133
|
+
storyLabel,
|
|
134
|
+
labelSuffix,
|
|
135
|
+
);
|
|
136
|
+
const existingOpenIssues = await this.issueRepository.searchIssue({
|
|
137
|
+
owner: issue.org,
|
|
138
|
+
repositoryName: issue.repo,
|
|
139
|
+
type: 'issue',
|
|
140
|
+
state: 'open',
|
|
141
|
+
title,
|
|
142
|
+
});
|
|
143
|
+
const alreadyNotified = existingOpenIssues.some(
|
|
144
|
+
(existing) => existing.title === title,
|
|
145
|
+
);
|
|
146
|
+
if (alreadyNotified) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const body = this.buildUnmatchedStoryLabelBody(issue, storyLabel);
|
|
150
|
+
await this.issueRepository.createNewIssue(
|
|
151
|
+
issue.org,
|
|
152
|
+
issue.repo,
|
|
153
|
+
title,
|
|
154
|
+
body,
|
|
155
|
+
[issue.org],
|
|
156
|
+
[],
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
private buildUnmatchedStoryLabelBody = (
|
|
161
|
+
issue: Issue,
|
|
162
|
+
storyLabel: string,
|
|
163
|
+
): string => {
|
|
164
|
+
const labelSuffix = storyLabel.slice(
|
|
165
|
+
SetWorkflowManagementIssueToStoryUseCase.STORY_LABEL_PREFIX.length,
|
|
166
|
+
);
|
|
167
|
+
return [
|
|
168
|
+
'From: :robot: SetWorkflowManagementIssueToStoryUseCase',
|
|
169
|
+
'',
|
|
170
|
+
`The issue below carries the label \`${storyLabel}\`, but the project has no matching \`${SetWorkflowManagementIssueToStoryUseCase.REGULAR_STORY_PREFIX}${labelSuffix}\` Story option.`,
|
|
171
|
+
'',
|
|
172
|
+
issue.url,
|
|
173
|
+
'',
|
|
174
|
+
`Because no matching \`${SetWorkflowManagementIssueToStoryUseCase.REGULAR_STORY_PREFIX}${labelSuffix}\` Story option exists, the label cannot be auto-converted to a Story.`,
|
|
175
|
+
'Add the missing Story option to the project, or correct the label on the issue above, to resolve this.',
|
|
176
|
+
].join('\n');
|
|
177
|
+
};
|
|
178
|
+
|
|
119
179
|
private isEligibleIssue = (issue: Issue, targetDates: Date[]): boolean => {
|
|
120
180
|
const hasStoryOrWorkflowTrigger =
|
|
121
181
|
issue.labels.some((label) =>
|