github-issue-tower-defence-management 1.38.1 → 1.40.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.
- package/.github/workflows/api-created_issue_pr.yml +82 -0
- package/.github/workflows/create-pr.yml +12 -5
- package/.github/workflows/umino-project.yml +45 -16
- package/CHANGELOG.md +20 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +206 -6
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +7 -4
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +111 -16
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +324 -8
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +146 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +17 -3
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +982 -167
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +177 -26
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +3 -4
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +3 -4
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +5 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +2 -3
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
|
@@ -12,6 +12,7 @@ import { DateRepository } from './adapter-interfaces/DateRepository';
|
|
|
12
12
|
import { SpreadsheetRepository } from './adapter-interfaces/SpreadsheetRepository';
|
|
13
13
|
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
14
14
|
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
15
|
+
import { Issue } from '../entities/Issue';
|
|
15
16
|
import { Project } from '../entities/Project';
|
|
16
17
|
import { ChangeStatusByStoryColorUseCase } from './ChangeStatusByStoryColorUseCase';
|
|
17
18
|
import { SetNoStoryIssueToStoryUseCase } from './SetNoStoryIssueToStoryUseCase';
|
|
@@ -309,5 +310,150 @@ describe('HandleScheduledEventUseCase', () => {
|
|
|
309
310
|
120,
|
|
310
311
|
);
|
|
311
312
|
});
|
|
313
|
+
|
|
314
|
+
describe('story issue creation progress logs', () => {
|
|
315
|
+
const storyInput = {
|
|
316
|
+
projectName: 'test-project',
|
|
317
|
+
org: 'test-org',
|
|
318
|
+
projectUrl: 'https://github.com/test-org/test-project',
|
|
319
|
+
manager: 'test-manager',
|
|
320
|
+
workingReport: {
|
|
321
|
+
repo: 'test-repo',
|
|
322
|
+
members: ['member1'],
|
|
323
|
+
spreadsheetUrl: 'https://docs.google.com/spreadsheets/test',
|
|
324
|
+
},
|
|
325
|
+
urlOfStoryView: 'https://github.com/test-org/test-project/issues',
|
|
326
|
+
disabledStatus: 'disabled',
|
|
327
|
+
defaultStatus: null,
|
|
328
|
+
disabled: false,
|
|
329
|
+
allowIssueCacheMinutes: 60,
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const storyProject: Project = {
|
|
333
|
+
id: 'proj-1',
|
|
334
|
+
url: 'https://github.com/orgs/test-org/projects/1',
|
|
335
|
+
databaseId: 1,
|
|
336
|
+
name: 'test-project',
|
|
337
|
+
status: { name: 'Status', fieldId: 'f1', statuses: [] },
|
|
338
|
+
nextActionDate: null,
|
|
339
|
+
nextActionHour: null,
|
|
340
|
+
story: {
|
|
341
|
+
name: 'Story',
|
|
342
|
+
fieldId: 'f2',
|
|
343
|
+
databaseId: 2,
|
|
344
|
+
stories: [
|
|
345
|
+
{
|
|
346
|
+
id: 'story-1',
|
|
347
|
+
name: 'feature / StoryOne',
|
|
348
|
+
color: 'BLUE',
|
|
349
|
+
description: 'story desc',
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
workflowManagementStory: { id: 'wm-1', name: 'workflow' },
|
|
353
|
+
},
|
|
354
|
+
remainingEstimationMinutes: null,
|
|
355
|
+
dependedIssueUrlSeparatedByComma: null,
|
|
356
|
+
completionDate50PercentConfidence: null,
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const capturedLogs: string[] = [];
|
|
360
|
+
let consoleSpy: jest.SpyInstance;
|
|
361
|
+
|
|
362
|
+
beforeEach(() => {
|
|
363
|
+
capturedLogs.length = 0;
|
|
364
|
+
consoleSpy = jest
|
|
365
|
+
.spyOn(console, 'log')
|
|
366
|
+
.mockImplementation((...data: unknown[]) => {
|
|
367
|
+
const firstData = data[0];
|
|
368
|
+
if (
|
|
369
|
+
typeof firstData === 'string' &&
|
|
370
|
+
firstData.startsWith('[HandleScheduledEvent]')
|
|
371
|
+
) {
|
|
372
|
+
capturedLogs.push(firstData);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
jest.useFakeTimers();
|
|
376
|
+
mockProjectRepository.getProject.mockResolvedValue(storyProject);
|
|
377
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
378
|
+
issues: [],
|
|
379
|
+
cacheUsed: false,
|
|
380
|
+
});
|
|
381
|
+
mockIssueRepository.createNewIssue.mockResolvedValue(99);
|
|
382
|
+
const createdIssue = mock<Issue>();
|
|
383
|
+
createdIssue.itemId = 'item-99';
|
|
384
|
+
mockIssueRepository.getIssueByUrl.mockResolvedValue(createdIssue);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
afterEach(() => {
|
|
388
|
+
consoleSpy.mockRestore();
|
|
389
|
+
jest.useRealTimers();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should emit Creating story issue log before createNewIssue', async () => {
|
|
393
|
+
const runPromise = useCase.run(storyInput);
|
|
394
|
+
await jest.runAllTimersAsync();
|
|
395
|
+
await runPromise;
|
|
396
|
+
|
|
397
|
+
expect(capturedLogs[0]).toContain('Creating story issue');
|
|
398
|
+
expect(capturedLogs[0]).toContain('feature / StoryOne');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should emit Polling for issue log before each 30s sleep', async () => {
|
|
402
|
+
const runPromise = useCase.run(storyInput);
|
|
403
|
+
await jest.runAllTimersAsync();
|
|
404
|
+
await runPromise;
|
|
405
|
+
|
|
406
|
+
expect(capturedLogs[1]).toContain('Polling for issue (attempt 1/3)');
|
|
407
|
+
expect(capturedLogs[1]).toContain(
|
|
408
|
+
'https://github.com/test-org/test-repo/issues/99',
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('should emit Issue found log on successful issue lookup', async () => {
|
|
413
|
+
const runPromise = useCase.run(storyInput);
|
|
414
|
+
await jest.runAllTimersAsync();
|
|
415
|
+
await runPromise;
|
|
416
|
+
|
|
417
|
+
expect(capturedLogs[2]).toContain('Issue found');
|
|
418
|
+
expect(capturedLogs[2]).toContain(
|
|
419
|
+
'https://github.com/test-org/test-repo/issues/99',
|
|
420
|
+
);
|
|
421
|
+
expect(capturedLogs[2]).toContain('itemId=item-99');
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('should emit Waiting for story update log before 10s sleep', async () => {
|
|
425
|
+
const runPromise = useCase.run(storyInput);
|
|
426
|
+
await jest.runAllTimersAsync();
|
|
427
|
+
await runPromise;
|
|
428
|
+
|
|
429
|
+
expect(capturedLogs[3]).toContain('Waiting for story update');
|
|
430
|
+
expect(capturedLogs[3]).toContain(
|
|
431
|
+
'https://github.com/test-org/test-repo/issues/99',
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should emit Story issue created log with elapsed time after iteration completes', async () => {
|
|
436
|
+
const runPromise = useCase.run(storyInput);
|
|
437
|
+
await jest.runAllTimersAsync();
|
|
438
|
+
await runPromise;
|
|
439
|
+
|
|
440
|
+
expect(capturedLogs[4]).toContain('Story issue created');
|
|
441
|
+
expect(capturedLogs[4]).toContain('feature / StoryOne');
|
|
442
|
+
expect(capturedLogs[4]).toMatch(/elapsed=\d+ms/);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('should emit logs in expected order', async () => {
|
|
446
|
+
const runPromise = useCase.run(storyInput);
|
|
447
|
+
await jest.runAllTimersAsync();
|
|
448
|
+
await runPromise;
|
|
449
|
+
|
|
450
|
+
expect(capturedLogs).toHaveLength(5);
|
|
451
|
+
expect(capturedLogs[0]).toContain('Creating story issue');
|
|
452
|
+
expect(capturedLogs[1]).toContain('Polling for issue (attempt 1/3)');
|
|
453
|
+
expect(capturedLogs[2]).toContain('Issue found');
|
|
454
|
+
expect(capturedLogs[3]).toContain('Waiting for story update');
|
|
455
|
+
expect(capturedLogs[4]).toContain('Story issue created');
|
|
456
|
+
});
|
|
457
|
+
});
|
|
312
458
|
});
|
|
313
459
|
});
|
|
@@ -131,6 +131,10 @@ export class HandleScheduledEventUseCase {
|
|
|
131
131
|
) {
|
|
132
132
|
continue;
|
|
133
133
|
}
|
|
134
|
+
const storyStartTime = Date.now();
|
|
135
|
+
console.log(
|
|
136
|
+
`[HandleScheduledEvent] Creating story issue: story="${storyObject.story.name}"`,
|
|
137
|
+
);
|
|
134
138
|
const issueNumber = await this.issueRepository.createNewIssue(
|
|
135
139
|
input.org,
|
|
136
140
|
input.workingReport.repo,
|
|
@@ -142,13 +146,17 @@ export class HandleScheduledEventUseCase {
|
|
|
142
146
|
const issueUrl = `https://github.com/${input.org}/${input.workingReport.repo}/issues/${issueNumber}`;
|
|
143
147
|
let issue: Issue | null = null;
|
|
144
148
|
for (let i = 0; i < 3; i++) {
|
|
149
|
+
console.log(
|
|
150
|
+
`[HandleScheduledEvent] Polling for issue (attempt ${i + 1}/3): url=${issueUrl}`,
|
|
151
|
+
);
|
|
145
152
|
await new Promise((resolve) => setTimeout(resolve, 30 * 1000));
|
|
146
153
|
issue = await this.issueRepository.getIssueByUrl(issueUrl);
|
|
147
|
-
if (!issue) {
|
|
148
|
-
continue;
|
|
149
|
-
} else if (!issue.itemId) {
|
|
154
|
+
if (!issue || !issue.itemId) {
|
|
150
155
|
continue;
|
|
151
156
|
}
|
|
157
|
+
console.log(
|
|
158
|
+
`[HandleScheduledEvent] Issue found: url=${issueUrl} itemId=${issue.itemId}`,
|
|
159
|
+
);
|
|
152
160
|
break;
|
|
153
161
|
}
|
|
154
162
|
if (!issue) {
|
|
@@ -161,6 +169,9 @@ export class HandleScheduledEventUseCase {
|
|
|
161
169
|
issue,
|
|
162
170
|
storyObject.story.id,
|
|
163
171
|
);
|
|
172
|
+
console.log(
|
|
173
|
+
`[HandleScheduledEvent] Waiting for story update: url=${issueUrl}`,
|
|
174
|
+
);
|
|
164
175
|
await new Promise((resolve) => setTimeout(resolve, 10 * 1000));
|
|
165
176
|
const newIssue = await this.issueRepository.getIssueByUrl(issueUrl);
|
|
166
177
|
if (!newIssue) {
|
|
@@ -169,6 +180,9 @@ export class HandleScheduledEventUseCase {
|
|
|
169
180
|
storyObject.storyIssue = newIssue;
|
|
170
181
|
issues.push(newIssue);
|
|
171
182
|
storyObject.issues.push(newIssue);
|
|
183
|
+
console.log(
|
|
184
|
+
`[HandleScheduledEvent] Story issue created: story="${storyObject.story.name}" elapsed=${Date.now() - storyStartTime}ms`,
|
|
185
|
+
);
|
|
172
186
|
}
|
|
173
187
|
|
|
174
188
|
const targetDateTimes: Date[] =
|