github-issue-tower-defence-management 1.179.0 → 1.180.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/CHANGELOG.md +15 -0
- package/bin/adapter/entry-points/console/consoleOperationApi.js +42 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-I_2Yu8fM.js +86 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/console/webServer.js +6 -0
- package/bin/adapter/entry-points/console/webServer.js.map +1 -1
- package/bin/adapter/repositories/SystemDateRepository.js +10 -10
- package/bin/adapter/repositories/SystemDateRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +23 -4
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js +1 -1
- package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/AnalyzeStoriesUseCase.js +1 -1
- package/bin/domain/usecases/AnalyzeStoriesUseCase.js.map +1 -1
- package/bin/domain/usecases/ClearPastNextActionDateHourUseCase.js +1 -1
- package/bin/domain/usecases/ClearPastNextActionDateHourUseCase.js.map +1 -1
- package/bin/domain/usecases/CreateEstimationIssueUseCase.js +3 -3
- package/bin/domain/usecases/CreateEstimationIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +6 -6
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js +3 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/issueReactivationTriggerIsPending.js +2 -2
- package/bin/domain/usecases/issueReactivationTriggerIsPending.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +230 -0
- package/src/adapter/entry-points/console/consoleOperationApi.ts +48 -1
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +36 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +25 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.stories.tsx +30 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.test.tsx +111 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.tsx +91 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +21 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +24 -2
- package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +3 -1
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +60 -11
- package/src/adapter/entry-points/console/ui-dist/assets/index-I_2Yu8fM.js +86 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/console/webServer.ts +11 -0
- package/src/adapter/repositories/SystemDateRepository.test.ts +60 -0
- package/src/adapter/repositories/SystemDateRepository.ts +10 -10
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +268 -2
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +33 -4
- package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +1 -1
- package/src/domain/usecases/AnalyzeStoriesUseCase.test.ts +80 -0
- package/src/domain/usecases/AnalyzeStoriesUseCase.ts +1 -1
- package/src/domain/usecases/ClearPastNextActionDateHourUseCase.ts +6 -4
- package/src/domain/usecases/CreateEstimationIssueUseCase.test.ts +225 -0
- package/src/domain/usecases/CreateEstimationIssueUseCase.ts +3 -3
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +107 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +7 -7
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +83 -0
- package/src/domain/usecases/IssueRejectionEvaluator.ts +3 -1
- package/src/domain/usecases/StartPreparationUseCase.ts +5 -3
- package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +33 -33
- package/src/domain/usecases/issueReactivationTriggerIsPending.ts +6 -4
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts +3 -1
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/console/webServer.d.ts +2 -0
- package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/ClearPastNextActionDateHourUseCase.d.ts.map +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/issueReactivationTriggerIsPending.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-KCmqh781.js +0 -86
- package/src/adapter/entry-points/console/ui-dist/assets/index-KCmqh781.js +0 -86
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { mock } from 'jest-mock-extended';
|
|
2
|
+
import { CreateEstimationIssueUseCase } from './CreateEstimationIssueUseCase';
|
|
3
|
+
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
4
|
+
import { DateRepository } from './adapter-interfaces/DateRepository';
|
|
5
|
+
import { Project } from '../entities/Project';
|
|
6
|
+
import { StoryObject } from '../entities/StoryObjectMap';
|
|
7
|
+
import { Issue } from '../entities/Issue';
|
|
8
|
+
|
|
9
|
+
describe('CreateEstimationIssueUseCase', () => {
|
|
10
|
+
const mockIssueRepository = mock<IssueRepository>();
|
|
11
|
+
const mockDateRepository = mock<DateRepository>();
|
|
12
|
+
|
|
13
|
+
let useCase: CreateEstimationIssueUseCase;
|
|
14
|
+
|
|
15
|
+
const projectWithStory: Project = {
|
|
16
|
+
...mock<Project>(),
|
|
17
|
+
story: {
|
|
18
|
+
name: 'Story',
|
|
19
|
+
fieldId: 'story-field',
|
|
20
|
+
databaseId: 1,
|
|
21
|
+
stories: [],
|
|
22
|
+
workflowManagementStory: { id: 'wms-id', name: 'workflow management' },
|
|
23
|
+
},
|
|
24
|
+
remainingEstimationMinutes: null,
|
|
25
|
+
completionDate50PercentConfidence: null,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const commonInput = {
|
|
29
|
+
project: projectWithStory,
|
|
30
|
+
issues: [],
|
|
31
|
+
cacheUsed: false,
|
|
32
|
+
manager: 'manager-user',
|
|
33
|
+
org: 'org',
|
|
34
|
+
repo: 'repo',
|
|
35
|
+
urlOfStoryView: 'https://github.com/org/repo',
|
|
36
|
+
storyObjectMap: new Map<string, StoryObject>(),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
jest.clearAllMocks();
|
|
41
|
+
useCase = new CreateEstimationIssueUseCase(
|
|
42
|
+
mockIssueRepository,
|
|
43
|
+
mockDateRepository,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('run — UTC 07:00 guard', () => {
|
|
48
|
+
it('returns early when no targetDate is at UTC 07:00', async () => {
|
|
49
|
+
const nonMatchingDate = new Date(Date.UTC(2026, 0, 15, 6, 0, 0));
|
|
50
|
+
|
|
51
|
+
await useCase.run({ ...commonInput, targetDates: [nonMatchingDate] });
|
|
52
|
+
|
|
53
|
+
expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns early when targetDates is empty', async () => {
|
|
57
|
+
await useCase.run({ ...commonInput, targetDates: [] });
|
|
58
|
+
|
|
59
|
+
expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('returns early when project has no story field', async () => {
|
|
63
|
+
const projectWithoutStory: Project = { ...projectWithStory, story: null };
|
|
64
|
+
|
|
65
|
+
await useCase.run({
|
|
66
|
+
...commonInput,
|
|
67
|
+
project: projectWithoutStory,
|
|
68
|
+
targetDates: [new Date(Date.UTC(2026, 0, 15, 7, 0, 0))],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('run — weekend skip', () => {
|
|
76
|
+
const projectWithNonRegularStory: Project = {
|
|
77
|
+
...projectWithStory,
|
|
78
|
+
story: {
|
|
79
|
+
name: 'Story',
|
|
80
|
+
fieldId: 'story-field',
|
|
81
|
+
databaseId: 1,
|
|
82
|
+
stories: [
|
|
83
|
+
{
|
|
84
|
+
id: 'story-1',
|
|
85
|
+
name: 'Feature Story',
|
|
86
|
+
color: 'BLUE',
|
|
87
|
+
description: '',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
workflowManagementStory: { id: 'wms-id', name: 'workflow management' },
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
it('returns early when the last targetDate falls on Saturday UTC', async () => {
|
|
95
|
+
const saturdayAt07h = new Date(Date.UTC(2026, 0, 17, 7, 0, 0));
|
|
96
|
+
expect(saturdayAt07h.getUTCDay()).toBe(6);
|
|
97
|
+
|
|
98
|
+
await useCase.run({
|
|
99
|
+
...commonInput,
|
|
100
|
+
project: projectWithNonRegularStory,
|
|
101
|
+
targetDates: [saturdayAt07h],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('returns early when the last targetDate falls on Sunday UTC', async () => {
|
|
108
|
+
const sundayAt07h = new Date(Date.UTC(2026, 0, 18, 7, 0, 0));
|
|
109
|
+
expect(sundayAt07h.getUTCDay()).toBe(0);
|
|
110
|
+
|
|
111
|
+
await useCase.run({
|
|
112
|
+
...commonInput,
|
|
113
|
+
project: projectWithNonRegularStory,
|
|
114
|
+
targetDates: [sundayAt07h],
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(mockIssueRepository.createNewIssue).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('proceeds past the weekend guard on a weekday UTC 07:00', async () => {
|
|
121
|
+
const thursdayAt07h = new Date(Date.UTC(2026, 0, 15, 7, 0, 0));
|
|
122
|
+
expect(thursdayAt07h.getUTCDay()).toBe(4);
|
|
123
|
+
|
|
124
|
+
await expect(
|
|
125
|
+
useCase.run({
|
|
126
|
+
...commonInput,
|
|
127
|
+
project: projectWithNonRegularStory,
|
|
128
|
+
targetDates: [thursdayAt07h],
|
|
129
|
+
}),
|
|
130
|
+
).rejects.toThrow('Story issue not found: Feature Story');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('run — completionDate50PercentConfidence Monday UTC reset', () => {
|
|
135
|
+
const mondayAt07hUTC = new Date(Date.UTC(2026, 0, 12, 7, 0, 0));
|
|
136
|
+
|
|
137
|
+
const featureStory = {
|
|
138
|
+
id: 'story-feat',
|
|
139
|
+
name: 'Feature Story',
|
|
140
|
+
color: 'BLUE' as const,
|
|
141
|
+
description: '',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const projectWithCompletionField: Project = {
|
|
145
|
+
...mock<Project>(),
|
|
146
|
+
story: {
|
|
147
|
+
name: 'Story',
|
|
148
|
+
fieldId: 'story-field',
|
|
149
|
+
databaseId: 1,
|
|
150
|
+
stories: [featureStory],
|
|
151
|
+
workflowManagementStory: { id: 'wms-id', name: 'workflow management' },
|
|
152
|
+
},
|
|
153
|
+
remainingEstimationMinutes: null,
|
|
154
|
+
completionDate50PercentConfidence: {
|
|
155
|
+
name: 'Completion Date',
|
|
156
|
+
fieldId: 'completion-field',
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const storyIssue: Issue = {
|
|
161
|
+
...mock<Issue>(),
|
|
162
|
+
title: 'Feature Story',
|
|
163
|
+
labels: ['story', 'story:action:schedule-control'],
|
|
164
|
+
isClosed: false,
|
|
165
|
+
isPr: false,
|
|
166
|
+
url: 'https://github.com/org/repo/issues/1',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const farFutureDate = new Date(Date.UTC(2026, 5, 1, 0, 0, 0));
|
|
170
|
+
const issueInStory: Issue = {
|
|
171
|
+
...mock<Issue>(),
|
|
172
|
+
title: 'Task issue',
|
|
173
|
+
labels: [],
|
|
174
|
+
isClosed: false,
|
|
175
|
+
isPr: false,
|
|
176
|
+
status: null,
|
|
177
|
+
estimationMinutes: null,
|
|
178
|
+
completionDate50PercentConfidence: farFutureDate,
|
|
179
|
+
assignees: ['dev-user'],
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
beforeEach(() => {
|
|
183
|
+
jest.useFakeTimers();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
afterEach(() => {
|
|
187
|
+
jest.useRealTimers();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('clears completionDate50PercentConfidence on UTC Monday even when date is far in future', async () => {
|
|
191
|
+
expect(mondayAt07hUTC.getUTCDay()).toBe(1);
|
|
192
|
+
|
|
193
|
+
mockDateRepository.formatDateWithDayOfWeek.mockReturnValue(
|
|
194
|
+
'Mon, Jun 01, 2026',
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const storyObjectMap = new Map<string, StoryObject>([
|
|
198
|
+
[
|
|
199
|
+
featureStory.name,
|
|
200
|
+
{ story: featureStory, storyIssue, issues: [issueInStory] },
|
|
201
|
+
],
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
const runPromise = useCase.run({
|
|
205
|
+
project: projectWithCompletionField,
|
|
206
|
+
issues: [storyIssue],
|
|
207
|
+
cacheUsed: false,
|
|
208
|
+
manager: 'manager-user',
|
|
209
|
+
org: 'org',
|
|
210
|
+
repo: 'repo',
|
|
211
|
+
urlOfStoryView: 'https://github.com/org/repo',
|
|
212
|
+
storyObjectMap,
|
|
213
|
+
targetDates: [mondayAt07hUTC],
|
|
214
|
+
});
|
|
215
|
+
await jest.runAllTimersAsync();
|
|
216
|
+
await runPromise;
|
|
217
|
+
|
|
218
|
+
expect(mockIssueRepository.clearProjectField).toHaveBeenCalledWith(
|
|
219
|
+
projectWithCompletionField,
|
|
220
|
+
'completion-field',
|
|
221
|
+
issueInStory,
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -32,7 +32,7 @@ export class CreateEstimationIssueUseCase {
|
|
|
32
32
|
!story ||
|
|
33
33
|
!input.targetDates.find(
|
|
34
34
|
(targetDate) =>
|
|
35
|
-
targetDate.
|
|
35
|
+
targetDate.getUTCHours() === 7 && targetDate.getUTCMinutes() === 0,
|
|
36
36
|
)
|
|
37
37
|
) {
|
|
38
38
|
return;
|
|
@@ -41,7 +41,7 @@ export class CreateEstimationIssueUseCase {
|
|
|
41
41
|
if (!targetDate) {
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
if (targetDate.
|
|
44
|
+
if (targetDate.getUTCDay() === 0 || targetDate.getUTCDay() === 6) {
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -91,7 +91,7 @@ export class CreateEstimationIssueUseCase {
|
|
|
91
91
|
!!issueInStory.completionDate50PercentConfidence &&
|
|
92
92
|
(issueInStory.completionDate50PercentConfidence.getTime() <
|
|
93
93
|
targetDate.getTime() + 7 * 24 * 60 * 60 * 1000 ||
|
|
94
|
-
targetDate.
|
|
94
|
+
targetDate.getUTCDay() === 1)
|
|
95
95
|
) {
|
|
96
96
|
await this.issueRepository.createComment(
|
|
97
97
|
issueInStory,
|
|
@@ -1617,4 +1617,111 @@ describe('HandleScheduledEventUseCase', () => {
|
|
|
1617
1617
|
});
|
|
1618
1618
|
});
|
|
1619
1619
|
});
|
|
1620
|
+
|
|
1621
|
+
describe('storyIssues', () => {
|
|
1622
|
+
const useCase = new HandleScheduledEventUseCase(
|
|
1623
|
+
mock<ProjectRequiredFieldCreateUseCase>(),
|
|
1624
|
+
mock<SetupTowerDefenceProjectUseCase>(),
|
|
1625
|
+
mock<ActionAnnouncementUseCase>(),
|
|
1626
|
+
mock<SetWorkflowManagementIssueToStoryUseCase>(),
|
|
1627
|
+
mock<ClearPastNextActionDateHourUseCase>(),
|
|
1628
|
+
mock<AnalyzeProblemByIssueUseCase>(),
|
|
1629
|
+
mock<AnalyzeStoriesUseCase>(),
|
|
1630
|
+
mock<ClearDependedIssueURLUseCase>(),
|
|
1631
|
+
mock<SetDependedIssueUrlForOpenTaskPRsUseCase>(),
|
|
1632
|
+
mock<StaleTaskPullRequestCloseUseCase>(),
|
|
1633
|
+
mock<CreateEstimationIssueUseCase>(),
|
|
1634
|
+
mock<ConvertCheckboxToIssueInStoryIssueUseCase>(),
|
|
1635
|
+
mock<ChangeStatusByStoryColorUseCase>(),
|
|
1636
|
+
mock<SetNoStoryIssueToStoryUseCase>(),
|
|
1637
|
+
mock<CreateNewStoryByLabelUseCase>(),
|
|
1638
|
+
mock<AssignNoAssigneeIssueToManagerUseCase>(),
|
|
1639
|
+
mock<UpdateIssueStatusByLabelUseCase>(),
|
|
1640
|
+
mock<StartPreparationUseCase>(),
|
|
1641
|
+
mock<RevertOrphanedPreparationUseCase>(),
|
|
1642
|
+
mock<RevertNotReadyReviewQueueIssueUseCase>(),
|
|
1643
|
+
mock<TriagerApprovalDispatchUseCase>(),
|
|
1644
|
+
mock<AgentDesignationLabelAdoptUseCase>(),
|
|
1645
|
+
null,
|
|
1646
|
+
null,
|
|
1647
|
+
mock<QualityCheckAdvanceUseCase>(),
|
|
1648
|
+
mock<DateRepository>(),
|
|
1649
|
+
mock<SpreadsheetRepository>(),
|
|
1650
|
+
mock<ProjectRepository>(),
|
|
1651
|
+
mock<IssueRepository>(),
|
|
1652
|
+
);
|
|
1653
|
+
|
|
1654
|
+
const storyName = 'my story / feature';
|
|
1655
|
+
const baseProject: Project = {
|
|
1656
|
+
id: 'project-1',
|
|
1657
|
+
url: 'https://github.com/orgs/user/projects/1',
|
|
1658
|
+
databaseId: 1,
|
|
1659
|
+
name: 'Test Project',
|
|
1660
|
+
status: { name: 'Status', fieldId: 'status-field-id', statuses: [] },
|
|
1661
|
+
nextActionDate: null,
|
|
1662
|
+
nextActionHour: null,
|
|
1663
|
+
story: {
|
|
1664
|
+
name: 'Story',
|
|
1665
|
+
fieldId: 'story-field-id',
|
|
1666
|
+
databaseId: 1,
|
|
1667
|
+
stories: [
|
|
1668
|
+
{ id: 'story-1', name: storyName, color: 'BLUE', description: '' },
|
|
1669
|
+
],
|
|
1670
|
+
workflowManagementStory: { id: 'wms-1', name: 'workflow management' },
|
|
1671
|
+
},
|
|
1672
|
+
remainingEstimationMinutes: null,
|
|
1673
|
+
dependedIssueUrlSeparatedByComma: null,
|
|
1674
|
+
completionDate50PercentConfidence: null,
|
|
1675
|
+
agent: null,
|
|
1676
|
+
};
|
|
1677
|
+
|
|
1678
|
+
const baseIssue: Issue = {
|
|
1679
|
+
nameWithOwner: 'user/repo',
|
|
1680
|
+
number: 1,
|
|
1681
|
+
title: storyName,
|
|
1682
|
+
state: 'OPEN',
|
|
1683
|
+
status: null,
|
|
1684
|
+
story: null,
|
|
1685
|
+
nextActionDate: null,
|
|
1686
|
+
nextActionHour: null,
|
|
1687
|
+
estimationMinutes: null,
|
|
1688
|
+
dependedIssueUrls: [],
|
|
1689
|
+
completionDate50PercentConfidence: null,
|
|
1690
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1691
|
+
assignees: [],
|
|
1692
|
+
labels: [],
|
|
1693
|
+
org: 'user',
|
|
1694
|
+
repo: 'repo',
|
|
1695
|
+
body: '',
|
|
1696
|
+
itemId: 'item-1',
|
|
1697
|
+
isPr: false,
|
|
1698
|
+
isInProgress: false,
|
|
1699
|
+
isClosed: false,
|
|
1700
|
+
createdAt: new Date('2024-01-01T00:00:00Z'),
|
|
1701
|
+
author: '',
|
|
1702
|
+
closingIssueReferenceUrls: [],
|
|
1703
|
+
agent: null,
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
it('should return storyIssue as null when the only matching issue is closed', async () => {
|
|
1707
|
+
const closedIssue: Issue = {
|
|
1708
|
+
...baseIssue,
|
|
1709
|
+
state: 'CLOSED',
|
|
1710
|
+
isClosed: true,
|
|
1711
|
+
};
|
|
1712
|
+
const result = await useCase.storyIssues({
|
|
1713
|
+
project: baseProject,
|
|
1714
|
+
issues: [closedIssue],
|
|
1715
|
+
});
|
|
1716
|
+
expect(result.get(storyName)?.storyIssue).toBeNull();
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
it('should return the open matching issue as storyIssue', async () => {
|
|
1720
|
+
const result = await useCase.storyIssues({
|
|
1721
|
+
project: baseProject,
|
|
1722
|
+
issues: [baseIssue],
|
|
1723
|
+
});
|
|
1724
|
+
expect(result.get(storyName)?.storyIssue).toBe(baseIssue);
|
|
1725
|
+
});
|
|
1726
|
+
});
|
|
1620
1727
|
});
|
|
@@ -622,20 +622,20 @@ ${JSON.stringify(e)}
|
|
|
622
622
|
const targetDateTimes: Date[] = [];
|
|
623
623
|
if (from.getTime() > to.getTime()) {
|
|
624
624
|
const targetDate = new Date(to);
|
|
625
|
-
targetDate.
|
|
626
|
-
targetDate.
|
|
625
|
+
targetDate.setUTCSeconds(0);
|
|
626
|
+
targetDate.setUTCMilliseconds(0);
|
|
627
627
|
return [targetDate];
|
|
628
628
|
}
|
|
629
629
|
const targetDate = new Date(from);
|
|
630
630
|
targetDate.setTime(targetDate.getTime() + 60 * 1000);
|
|
631
|
-
targetDate.
|
|
632
|
-
targetDate.
|
|
631
|
+
targetDate.setUTCSeconds(0);
|
|
632
|
+
targetDate.setUTCMilliseconds(0);
|
|
633
633
|
while (
|
|
634
634
|
targetDate.getTime() <= to.getTime() &&
|
|
635
635
|
targetDateTimes.length < 300
|
|
636
636
|
) {
|
|
637
637
|
targetDateTimes.push(new Date(targetDate));
|
|
638
|
-
targetDate.
|
|
638
|
+
targetDate.setUTCMinutes(targetDate.getUTCMinutes() + 1);
|
|
639
639
|
}
|
|
640
640
|
return targetDateTimes;
|
|
641
641
|
};
|
|
@@ -816,8 +816,8 @@ ${JSON.stringify(e)}
|
|
|
816
816
|
const summaryStoryIssue: StoryObjectMap = new Map();
|
|
817
817
|
const targetStory = input.project.story?.stories || [];
|
|
818
818
|
for (const story of targetStory) {
|
|
819
|
-
const storyIssue = input.issues.find(
|
|
820
|
-
story.name.startsWith(issue.title),
|
|
819
|
+
const storyIssue = input.issues.find(
|
|
820
|
+
(issue) => story.name.startsWith(issue.title) && !issue.isClosed,
|
|
821
821
|
);
|
|
822
822
|
summaryStoryIssue.set(story.name, {
|
|
823
823
|
story,
|
|
@@ -258,6 +258,89 @@ describe('IssueRejectionEvaluator', () => {
|
|
|
258
258
|
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
259
259
|
});
|
|
260
260
|
|
|
261
|
+
it('should require PR evaluation and reject CI failure when issue agent is pr-reviewer', async () => {
|
|
262
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
263
|
+
createReadyPr('https://github.com/user/repo/pull/10', {
|
|
264
|
+
isPassedAllCiJob: false,
|
|
265
|
+
isCiStateSuccess: false,
|
|
266
|
+
}),
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
const result = await evaluator.evaluate({
|
|
270
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
271
|
+
labels: [],
|
|
272
|
+
isPr: false,
|
|
273
|
+
agent: 'pr-reviewer',
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
|
|
277
|
+
expect(result.rejections.map((r) => r.type)).toContain(
|
|
278
|
+
'ANY_CI_JOB_FAILED_OR_IN_PROGRESS',
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should require PR evaluation and reject conflict when issue agent is pr-reviewer', async () => {
|
|
283
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
284
|
+
createReadyPr('https://github.com/user/repo/pull/10', {
|
|
285
|
+
isConflicted: true,
|
|
286
|
+
}),
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
const result = await evaluator.evaluate({
|
|
290
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
291
|
+
labels: [],
|
|
292
|
+
isPr: false,
|
|
293
|
+
agent: 'pr-reviewer',
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
|
|
297
|
+
expect(result.rejections.map((r) => r.type)).toContain(
|
|
298
|
+
'PULL_REQUEST_CONFLICTED',
|
|
299
|
+
);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should require PR evaluation and approve when issue agent is pr-reviewer with passing CI and no conflict', async () => {
|
|
303
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
304
|
+
createReadyPr('https://github.com/user/repo/pull/10'),
|
|
305
|
+
]);
|
|
306
|
+
|
|
307
|
+
const result = await evaluator.evaluate({
|
|
308
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
309
|
+
labels: [],
|
|
310
|
+
isPr: false,
|
|
311
|
+
agent: 'pr-reviewer',
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
|
|
315
|
+
expect(result.rejections).toHaveLength(0);
|
|
316
|
+
expect(result.approvedPrUrl).toBe('https://github.com/user/repo/pull/10');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should still require PR evaluation for pr-reviewer when custom developerAgentName is set', async () => {
|
|
320
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
321
|
+
createReadyPr('https://github.com/user/repo/pull/10', {
|
|
322
|
+
isPassedAllCiJob: false,
|
|
323
|
+
isCiStateSuccess: false,
|
|
324
|
+
}),
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
const result = await evaluator.evaluate(
|
|
328
|
+
{
|
|
329
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
330
|
+
labels: [],
|
|
331
|
+
isPr: false,
|
|
332
|
+
agent: 'pr-reviewer',
|
|
333
|
+
},
|
|
334
|
+
[],
|
|
335
|
+
{ developerAgentName: 'my-developer' },
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
|
|
339
|
+
expect(result.rejections.map((r) => r.type)).toContain(
|
|
340
|
+
'ANY_CI_JOB_FAILED_OR_IN_PROGRESS',
|
|
341
|
+
);
|
|
342
|
+
});
|
|
343
|
+
|
|
261
344
|
it('should not reject for draft state when issue has non-e2e category label', async () => {
|
|
262
345
|
const result = await evaluator.evaluate({
|
|
263
346
|
url: 'https://github.com/user/repo/issues/1',
|
|
@@ -204,7 +204,9 @@ export class IssueRejectionEvaluator {
|
|
|
204
204
|
);
|
|
205
205
|
const effectiveDeveloperAgentName = developerAgentName ?? 'developer';
|
|
206
206
|
const isNonDeveloperAgent =
|
|
207
|
-
issue.agent != null &&
|
|
207
|
+
issue.agent != null &&
|
|
208
|
+
issue.agent !== effectiveDeveloperAgentName &&
|
|
209
|
+
issue.agent !== 'pr-reviewer';
|
|
208
210
|
const hasLabelNotRequiringPullRequest = issue.labels.some((label) =>
|
|
209
211
|
labelsNotRequiringPullRequest.includes(label),
|
|
210
212
|
);
|
|
@@ -453,9 +453,11 @@ export class StartPreparationUseCase {
|
|
|
453
453
|
}
|
|
454
454
|
if (issueReactivationTriggerIsPending(issue, now)) {
|
|
455
455
|
const startOfTomorrow = new Date(
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
456
|
+
Date.UTC(
|
|
457
|
+
now.getUTCFullYear(),
|
|
458
|
+
now.getUTCMonth(),
|
|
459
|
+
now.getUTCDate() + 1,
|
|
460
|
+
),
|
|
459
461
|
);
|
|
460
462
|
if (
|
|
461
463
|
issue.nextActionDate !== null &&
|