github-issue-tower-defence-management 1.163.0 → 1.165.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 +24 -0
- package/README.md +1 -1
- package/bin/adapter/entry-points/cli/index.js +1 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/cli/projectConfig.js +2 -0
- package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js +1 -0
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js +1 -0
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js.map +1 -1
- package/bin/adapter/repositories/GraphqlProjectRepository.js +37 -0
- package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +59 -5
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/RestIssueRepository.js +24 -0
- package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/projectFieldDefinition.js +8 -0
- package/bin/adapter/repositories/projectFieldDefinition.js.map +1 -1
- package/bin/domain/entities/RequiredProjectField.js +7 -1
- package/bin/domain/entities/RequiredProjectField.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -0
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +67 -0
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js +28 -2
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +5 -4
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/lib/typescript-compat/bin/tsc +2 -0
- package/lib/typescript-compat/bin/tsserver +2 -0
- package/lib/typescript-compat/lib/typescript.js +1 -0
- package/lib/typescript-compat/package.json +13 -0
- package/package.json +4 -3
- package/src/adapter/entry-points/cli/index.test.ts +2 -0
- package/src/adapter/entry-points/cli/index.ts +1 -0
- package/src/adapter/entry-points/cli/projectConfig.ts +3 -0
- package/src/adapter/entry-points/console/consoleOperationApi.ts +1 -0
- package/src/adapter/entry-points/console/consoleTabNames.test.ts +1 -0
- package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +1 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +4 -0
- package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +1 -0
- package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +2 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +1 -0
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +1 -0
- package/src/adapter/entry-points/handlers/ownerCallFileCleaner.test.ts +1 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +2 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +1 -0
- package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +1 -0
- package/src/adapter/repositories/GraphqlProjectRepository.diskCache.test.ts +1 -0
- package/src/adapter/repositories/GraphqlProjectRepository.test.ts +2 -0
- package/src/adapter/repositories/GraphqlProjectRepository.ts +50 -0
- package/src/adapter/repositories/GraphqlProjectRepositoryProjectLocation.test.ts +1 -0
- package/src/adapter/repositories/ProjectIssuesCacheRepository.test.ts +1 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +156 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +99 -6
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +1 -0
- package/src/adapter/repositories/issue/RestIssueRepository.ts +30 -0
- package/src/adapter/repositories/projectFieldDefinition.test.ts +1 -0
- package/src/adapter/repositories/projectFieldDefinition.ts +12 -0
- package/src/domain/entities/Issue.ts +1 -0
- package/src/domain/entities/Project.ts +5 -0
- package/src/domain/entities/RequiredProjectField.ts +6 -0
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +1 -0
- package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +2 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +1 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +2 -0
- package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +1 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +381 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +117 -1
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +1 -0
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +75 -1
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +50 -7
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +2 -0
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +2 -0
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +2 -0
- package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +2 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +2 -0
- package/src/domain/usecases/StartPreparationUseCase.ts +1 -0
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +10 -0
- package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +6 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +2 -0
- package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +1 -0
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +2 -0
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +1 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
- package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/tokenStatusWriter.d.ts.map +1 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts +3 -0
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +5 -2
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/RestIssueRepository.d.ts +1 -0
- package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/projectFieldDefinition.d.ts.map +1 -1
- package/types/domain/entities/Issue.d.ts +1 -0
- package/types/domain/entities/Issue.d.ts.map +1 -1
- package/types/domain/entities/Project.d.ts +5 -0
- package/types/domain/entities/Project.d.ts.map +1 -1
- package/types/domain/entities/RequiredProjectField.d.ts +1 -0
- package/types/domain/entities/RequiredProjectField.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -0
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +4 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts +5 -1
- package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +2 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +3 -0
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -1
|
@@ -72,6 +72,7 @@ describeWhenCredentials('GraphqlProjectRepository', () => {
|
|
|
72
72
|
name: 'Workflow Management',
|
|
73
73
|
},
|
|
74
74
|
},
|
|
75
|
+
agent: null,
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
it('should add a new option while preserving all existing options', async () => {
|
|
@@ -151,6 +152,7 @@ describeWhenCredentials('GraphqlProjectRepository', () => {
|
|
|
151
152
|
},
|
|
152
153
|
completionDate50PercentConfidence: null,
|
|
153
154
|
dependedIssueUrlSeparatedByComma: null,
|
|
155
|
+
agent: null,
|
|
154
156
|
|
|
155
157
|
status: {
|
|
156
158
|
fieldId: 'PVTSSF_lAHOAGJHa84AFhgFzgDLt0c',
|
|
@@ -601,6 +601,56 @@ export class GraphqlProjectRepository
|
|
|
601
601
|
);
|
|
602
602
|
return options;
|
|
603
603
|
};
|
|
604
|
+
updateAgentList = async (
|
|
605
|
+
project: Project,
|
|
606
|
+
newAgentList: (Omit<FieldOption, 'id'> & {
|
|
607
|
+
id: FieldOption['id'] | null;
|
|
608
|
+
})[],
|
|
609
|
+
): Promise<FieldOption[]> => {
|
|
610
|
+
if (!project.agent) {
|
|
611
|
+
throw new Error('Project has no agent field');
|
|
612
|
+
}
|
|
613
|
+
const mutation = `mutation UpdateAgentOptions($fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]!) {
|
|
614
|
+
updateProjectV2Field(input: {
|
|
615
|
+
fieldId: $fieldId
|
|
616
|
+
singleSelectOptions: $options
|
|
617
|
+
}) {
|
|
618
|
+
projectV2Field {
|
|
619
|
+
... on ProjectV2SingleSelectField {
|
|
620
|
+
options {
|
|
621
|
+
id
|
|
622
|
+
name
|
|
623
|
+
color
|
|
624
|
+
description
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}`;
|
|
630
|
+
const variables = {
|
|
631
|
+
fieldId: project.agent.fieldId,
|
|
632
|
+
options: newAgentList.map(({ id, name, color, description }) => ({
|
|
633
|
+
...(id !== null ? { id } : {}),
|
|
634
|
+
name,
|
|
635
|
+
color,
|
|
636
|
+
description,
|
|
637
|
+
})),
|
|
638
|
+
};
|
|
639
|
+
const response = await postGithubGraphqlJson<{
|
|
640
|
+
data: {
|
|
641
|
+
updateProjectV2Field: {
|
|
642
|
+
projectV2Field: {
|
|
643
|
+
options: FieldOption[];
|
|
644
|
+
};
|
|
645
|
+
};
|
|
646
|
+
};
|
|
647
|
+
}>({
|
|
648
|
+
ghToken: this.ghToken,
|
|
649
|
+
query: mutation,
|
|
650
|
+
variables,
|
|
651
|
+
});
|
|
652
|
+
return response.data.updateProjectV2Field.projectV2Field.options;
|
|
653
|
+
};
|
|
604
654
|
updateStatusList = async (
|
|
605
655
|
project: Project,
|
|
606
656
|
newStatusList: (Omit<FieldOption, 'id'> & {
|
|
@@ -186,6 +186,7 @@ describe('GraphqlProjectRepository project location', () => {
|
|
|
186
186
|
remainingEstimationMinutes: null,
|
|
187
187
|
dependedIssueUrlSeparatedByComma: null,
|
|
188
188
|
completionDate50PercentConfidence: null,
|
|
189
|
+
agent: null,
|
|
189
190
|
});
|
|
190
191
|
|
|
191
192
|
expect(mockPostGithubGraphqlJson).not.toHaveBeenCalled();
|
|
@@ -28,6 +28,7 @@ const buildTestProject = (id: string): Project => ({
|
|
|
28
28
|
remainingEstimationMinutes: null,
|
|
29
29
|
dependedIssueUrlSeparatedByComma: null,
|
|
30
30
|
completionDate50PercentConfidence: null,
|
|
31
|
+
agent: null,
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
const buildCachedIssueRecord = (url: string, title: string) => ({
|
|
@@ -151,6 +152,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
151
152
|
closingIssueReferenceUrls: [
|
|
152
153
|
'https://github.com/HiromiShikata/test-repository/issues/7',
|
|
153
154
|
],
|
|
155
|
+
agent: null,
|
|
154
156
|
},
|
|
155
157
|
},
|
|
156
158
|
{
|
|
@@ -207,6 +209,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
207
209
|
createdAt: new Date('2024-01-01T00:00:00Z'),
|
|
208
210
|
author: '',
|
|
209
211
|
closingIssueReferenceUrls: [],
|
|
212
|
+
agent: null,
|
|
210
213
|
},
|
|
211
214
|
},
|
|
212
215
|
];
|
|
@@ -716,6 +719,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
716
719
|
remainingEstimationMinutes: null,
|
|
717
720
|
dependedIssueUrlSeparatedByComma: null,
|
|
718
721
|
completionDate50PercentConfidence: null,
|
|
722
|
+
agent: null,
|
|
719
723
|
};
|
|
720
724
|
const issue = {
|
|
721
725
|
nameWithOwner: 'HiromiShikata/test-repository',
|
|
@@ -742,6 +746,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
742
746
|
createdAt: new Date('2024-01-01'),
|
|
743
747
|
author: '',
|
|
744
748
|
closingIssueReferenceUrls: [],
|
|
749
|
+
agent: null,
|
|
745
750
|
};
|
|
746
751
|
const statusId = 'new-status-id';
|
|
747
752
|
|
|
@@ -1171,6 +1176,157 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
1171
1176
|
});
|
|
1172
1177
|
});
|
|
1173
1178
|
|
|
1179
|
+
describe('mergePullRequest', () => {
|
|
1180
|
+
afterEach(() => {
|
|
1181
|
+
jest.restoreAllMocks();
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
it('should PUT to the GitHub merge API with no merge_method when the repository allows merge commits', async () => {
|
|
1185
|
+
const fetchSpy = jest
|
|
1186
|
+
.spyOn(global, 'fetch')
|
|
1187
|
+
.mockResolvedValueOnce(new Response(null, { status: 204 }));
|
|
1188
|
+
|
|
1189
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1190
|
+
await repository.mergePullRequest(
|
|
1191
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1192
|
+
);
|
|
1193
|
+
|
|
1194
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
1195
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
1196
|
+
'https://api.github.com/repos/HiromiShikata/test-repository/pulls/42/merge',
|
|
1197
|
+
expect.objectContaining({
|
|
1198
|
+
method: 'PUT',
|
|
1199
|
+
body: JSON.stringify({}),
|
|
1200
|
+
}),
|
|
1201
|
+
);
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
it('should fall back to squash merge when the repository disallows merge commits (HTTP 405)', async () => {
|
|
1205
|
+
const fetchSpy = jest
|
|
1206
|
+
.spyOn(global, 'fetch')
|
|
1207
|
+
.mockResolvedValueOnce(
|
|
1208
|
+
new Response(
|
|
1209
|
+
JSON.stringify({
|
|
1210
|
+
message: 'Merge commits are not allowed on this repository.',
|
|
1211
|
+
}),
|
|
1212
|
+
{ status: 405, headers: { 'Content-Type': 'application/json' } },
|
|
1213
|
+
),
|
|
1214
|
+
)
|
|
1215
|
+
.mockResolvedValueOnce(
|
|
1216
|
+
new Response(
|
|
1217
|
+
JSON.stringify({
|
|
1218
|
+
allow_squash_merge: true,
|
|
1219
|
+
allow_rebase_merge: true,
|
|
1220
|
+
}),
|
|
1221
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
1222
|
+
),
|
|
1223
|
+
)
|
|
1224
|
+
.mockResolvedValueOnce(new Response(null, { status: 204 }));
|
|
1225
|
+
|
|
1226
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1227
|
+
await repository.mergePullRequest(
|
|
1228
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
expect(fetchSpy).toHaveBeenCalledTimes(3);
|
|
1232
|
+
expect(fetchSpy).toHaveBeenNthCalledWith(
|
|
1233
|
+
3,
|
|
1234
|
+
'https://api.github.com/repos/HiromiShikata/test-repository/pulls/42/merge',
|
|
1235
|
+
expect.objectContaining({
|
|
1236
|
+
method: 'PUT',
|
|
1237
|
+
body: JSON.stringify({ merge_method: 'squash' }),
|
|
1238
|
+
}),
|
|
1239
|
+
);
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
it('should fall back to rebase merge when squash merge is also disallowed', async () => {
|
|
1243
|
+
const fetchSpy = jest
|
|
1244
|
+
.spyOn(global, 'fetch')
|
|
1245
|
+
.mockResolvedValueOnce(
|
|
1246
|
+
new Response(
|
|
1247
|
+
JSON.stringify({
|
|
1248
|
+
message: 'Merge commits are not allowed on this repository.',
|
|
1249
|
+
}),
|
|
1250
|
+
{ status: 405, headers: { 'Content-Type': 'application/json' } },
|
|
1251
|
+
),
|
|
1252
|
+
)
|
|
1253
|
+
.mockResolvedValueOnce(
|
|
1254
|
+
new Response(
|
|
1255
|
+
JSON.stringify({
|
|
1256
|
+
allow_squash_merge: false,
|
|
1257
|
+
allow_rebase_merge: true,
|
|
1258
|
+
}),
|
|
1259
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
1260
|
+
),
|
|
1261
|
+
)
|
|
1262
|
+
.mockResolvedValueOnce(new Response(null, { status: 204 }));
|
|
1263
|
+
|
|
1264
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1265
|
+
await repository.mergePullRequest(
|
|
1266
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
expect(fetchSpy).toHaveBeenCalledTimes(3);
|
|
1270
|
+
expect(fetchSpy).toHaveBeenNthCalledWith(
|
|
1271
|
+
3,
|
|
1272
|
+
'https://api.github.com/repos/HiromiShikata/test-repository/pulls/42/merge',
|
|
1273
|
+
expect.objectContaining({
|
|
1274
|
+
method: 'PUT',
|
|
1275
|
+
body: JSON.stringify({ merge_method: 'rebase' }),
|
|
1276
|
+
}),
|
|
1277
|
+
);
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
it('should throw with the original error when no alternative merge method is available after HTTP 405', async () => {
|
|
1281
|
+
jest
|
|
1282
|
+
.spyOn(global, 'fetch')
|
|
1283
|
+
.mockResolvedValueOnce(
|
|
1284
|
+
new Response(
|
|
1285
|
+
JSON.stringify({
|
|
1286
|
+
message: 'Merge commits are not allowed on this repository.',
|
|
1287
|
+
}),
|
|
1288
|
+
{ status: 405, headers: { 'Content-Type': 'application/json' } },
|
|
1289
|
+
),
|
|
1290
|
+
)
|
|
1291
|
+
.mockResolvedValueOnce(
|
|
1292
|
+
new Response(
|
|
1293
|
+
JSON.stringify({
|
|
1294
|
+
allow_squash_merge: false,
|
|
1295
|
+
allow_rebase_merge: false,
|
|
1296
|
+
}),
|
|
1297
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
1298
|
+
),
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1302
|
+
await expect(
|
|
1303
|
+
repository.mergePullRequest(
|
|
1304
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1305
|
+
),
|
|
1306
|
+
).rejects.toThrow(
|
|
1307
|
+
'Failed to merge PR https://github.com/HiromiShikata/test-repository/pull/42: HTTP 405 Merge commits are not allowed on this repository.',
|
|
1308
|
+
);
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
it('should throw when the merge API responds with a non-405 error', async () => {
|
|
1312
|
+
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
1313
|
+
new Response(JSON.stringify({ message: 'Not Found' }), {
|
|
1314
|
+
status: 404,
|
|
1315
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1316
|
+
}),
|
|
1317
|
+
);
|
|
1318
|
+
|
|
1319
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
1320
|
+
await expect(
|
|
1321
|
+
repository.mergePullRequest(
|
|
1322
|
+
'https://github.com/HiromiShikata/test-repository/pull/42',
|
|
1323
|
+
),
|
|
1324
|
+
).rejects.toThrow(
|
|
1325
|
+
'Failed to merge PR https://github.com/HiromiShikata/test-repository/pull/42: HTTP 404 Not Found',
|
|
1326
|
+
);
|
|
1327
|
+
});
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1174
1330
|
describe('rate-limit-aware retry on console operations', () => {
|
|
1175
1331
|
afterEach(() => {
|
|
1176
1332
|
jest.restoreAllMocks();
|
|
@@ -362,6 +362,12 @@ function isRefContainer(value: unknown): value is { ref: string } {
|
|
|
362
362
|
return isRecord(value) && typeof value.ref === 'string';
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
function isRepoMergeSettings(
|
|
366
|
+
value: unknown,
|
|
367
|
+
): value is { allow_squash_merge?: boolean; allow_rebase_merge?: boolean } {
|
|
368
|
+
return isRecord(value);
|
|
369
|
+
}
|
|
370
|
+
|
|
365
371
|
type IssueOrPullRequestBodyResponse = {
|
|
366
372
|
body: string | null;
|
|
367
373
|
};
|
|
@@ -527,6 +533,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
527
533
|
| 'removeLabel'
|
|
528
534
|
| 'updateAssigneeList'
|
|
529
535
|
| 'searchIssues'
|
|
536
|
+
| 'getOrCreateLabel'
|
|
530
537
|
>,
|
|
531
538
|
readonly graphqlProjectItemRepository: Pick<
|
|
532
539
|
GraphqlProjectItemRepository,
|
|
@@ -614,6 +621,10 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
614
621
|
const status = item.customFields.find(
|
|
615
622
|
(field) => normalizeFieldName(field.name) === 'status',
|
|
616
623
|
)?.value;
|
|
624
|
+
const agent =
|
|
625
|
+
item.customFields.find(
|
|
626
|
+
(field) => normalizeFieldName(field.name) === 'agent',
|
|
627
|
+
)?.value ?? null;
|
|
617
628
|
const { owner, repo } = this.extractIssueFromUrl(item.url);
|
|
618
629
|
|
|
619
630
|
return {
|
|
@@ -643,6 +654,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
643
654
|
createdAt: new Date(item.createdAt || '2000-01-01'),
|
|
644
655
|
author: item.author,
|
|
645
656
|
closingIssueReferenceUrls: item.closingIssueReferenceUrls,
|
|
657
|
+
agent,
|
|
646
658
|
};
|
|
647
659
|
};
|
|
648
660
|
private restoreIssuesFromCache = (rawIssues: unknown): Issue[] | null => {
|
|
@@ -914,6 +926,33 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
914
926
|
);
|
|
915
927
|
};
|
|
916
928
|
|
|
929
|
+
setIssueAgentField = async (
|
|
930
|
+
issueUrl: string,
|
|
931
|
+
project: Project,
|
|
932
|
+
agentOptionId: string,
|
|
933
|
+
): Promise<void> => {
|
|
934
|
+
if (!project.agent) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const existingProjectItem =
|
|
938
|
+
await this.graphqlProjectItemRepository.fetchProjectItemByUrl(
|
|
939
|
+
issueUrl,
|
|
940
|
+
project.id,
|
|
941
|
+
);
|
|
942
|
+
const projectItemId =
|
|
943
|
+
existingProjectItem?.id ??
|
|
944
|
+
(await this.graphqlProjectItemRepository.addIssueToProject(
|
|
945
|
+
project.id,
|
|
946
|
+
issueUrl,
|
|
947
|
+
));
|
|
948
|
+
await this.graphqlProjectItemRepository.updateProjectField(
|
|
949
|
+
project.id,
|
|
950
|
+
project.agent.fieldId,
|
|
951
|
+
projectItemId,
|
|
952
|
+
{ singleSelectOptionId: agentOptionId },
|
|
953
|
+
);
|
|
954
|
+
};
|
|
955
|
+
|
|
917
956
|
updateNextActionDate = async (
|
|
918
957
|
issueUrl: string,
|
|
919
958
|
project: Project,
|
|
@@ -1006,6 +1045,13 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1006
1045
|
removeLabel = (issue: Issue, label: string): Promise<void> => {
|
|
1007
1046
|
return this.restIssueRepository.removeLabel(issue, label);
|
|
1008
1047
|
};
|
|
1048
|
+
getOrCreateLabel = (
|
|
1049
|
+
org: string,
|
|
1050
|
+
repo: string,
|
|
1051
|
+
labelName: string,
|
|
1052
|
+
): Promise<void> => {
|
|
1053
|
+
return this.restIssueRepository.getOrCreateLabel(org, repo, labelName);
|
|
1054
|
+
};
|
|
1009
1055
|
updateAssigneeList = (
|
|
1010
1056
|
issue: Pick<Issue, 'org' | 'repo' | 'number'>,
|
|
1011
1057
|
assigneeList: Member['name'][],
|
|
@@ -2239,24 +2285,71 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
2239
2285
|
|
|
2240
2286
|
mergePullRequest = async (prUrl: string): Promise<void> => {
|
|
2241
2287
|
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
2288
|
+
const mergeUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/merge`;
|
|
2289
|
+
const headers = {
|
|
2290
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
2291
|
+
'Content-Type': 'application/json',
|
|
2292
|
+
Accept: 'application/vnd.github+json',
|
|
2293
|
+
};
|
|
2294
|
+
const mergeWith = (mergeMethod?: 'squash' | 'rebase') =>
|
|
2295
|
+
this.fetchWithRateLimitRetry(() =>
|
|
2296
|
+
fetch(mergeUrl, {
|
|
2297
|
+
method: 'PUT',
|
|
2298
|
+
headers,
|
|
2299
|
+
body: JSON.stringify(
|
|
2300
|
+
mergeMethod ? { merge_method: mergeMethod } : {},
|
|
2301
|
+
),
|
|
2302
|
+
}),
|
|
2303
|
+
);
|
|
2304
|
+
const response = await mergeWith();
|
|
2305
|
+
if (response.ok) {
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
if (response.status === 405) {
|
|
2309
|
+
const fallbackMethod = await this.resolveAllowedMergeMethod(owner, repo);
|
|
2310
|
+
if (fallbackMethod !== null) {
|
|
2311
|
+
const retryResponse = await mergeWith(fallbackMethod);
|
|
2312
|
+
if (retryResponse.ok) {
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
const retryReason =
|
|
2316
|
+
await this.formatGitHubErrorWithStatus(retryResponse);
|
|
2317
|
+
throw new Error(`Failed to merge PR ${prUrl}: ${retryReason}`);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
2321
|
+
throw new Error(`Failed to merge PR ${prUrl}: ${reason}`);
|
|
2322
|
+
};
|
|
2323
|
+
|
|
2324
|
+
private resolveAllowedMergeMethod = async (
|
|
2325
|
+
owner: string,
|
|
2326
|
+
repo: string,
|
|
2327
|
+
): Promise<'squash' | 'rebase' | null> => {
|
|
2242
2328
|
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2243
2329
|
fetch(
|
|
2244
|
-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}
|
|
2330
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
|
|
2245
2331
|
{
|
|
2246
|
-
method: 'PUT',
|
|
2247
2332
|
headers: {
|
|
2248
2333
|
Authorization: `Bearer ${this.ghToken}`,
|
|
2249
|
-
'Content-Type': 'application/json',
|
|
2250
2334
|
Accept: 'application/vnd.github+json',
|
|
2251
2335
|
},
|
|
2252
|
-
body: JSON.stringify({}),
|
|
2253
2336
|
},
|
|
2254
2337
|
),
|
|
2255
2338
|
);
|
|
2256
2339
|
if (!response.ok) {
|
|
2257
|
-
|
|
2258
|
-
|
|
2340
|
+
return null;
|
|
2341
|
+
}
|
|
2342
|
+
const data: unknown = await response.json();
|
|
2343
|
+
if (!isRepoMergeSettings(data)) {
|
|
2344
|
+
return null;
|
|
2259
2345
|
}
|
|
2346
|
+
if (data.allow_squash_merge === true) {
|
|
2347
|
+
return 'squash';
|
|
2348
|
+
}
|
|
2349
|
+
if (data.allow_rebase_merge === true) {
|
|
2350
|
+
return 'rebase';
|
|
2351
|
+
}
|
|
2352
|
+
return null;
|
|
2260
2353
|
};
|
|
2261
2354
|
|
|
2262
2355
|
requestChangesWithInlineComment = async (
|
|
@@ -154,6 +154,36 @@ export class RestIssueRepository
|
|
|
154
154
|
}
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
+
getOrCreateLabel = async (
|
|
158
|
+
org: string,
|
|
159
|
+
repo: string,
|
|
160
|
+
labelName: string,
|
|
161
|
+
): Promise<void> => {
|
|
162
|
+
try {
|
|
163
|
+
await ky.get(
|
|
164
|
+
`https://api.github.com/repos/${org}/${repo}/labels/${encodeURIComponent(labelName)}`,
|
|
165
|
+
{
|
|
166
|
+
headers: {
|
|
167
|
+
Authorization: `token ${this.ghToken}`,
|
|
168
|
+
Accept: 'application/vnd.github.v3+json',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (e instanceof HTTPError && e.response.status === 404) {
|
|
174
|
+
await ky.post(`https://api.github.com/repos/${org}/${repo}/labels`, {
|
|
175
|
+
json: { name: labelName, color: 'ededed' },
|
|
176
|
+
headers: {
|
|
177
|
+
Authorization: `token ${this.ghToken}`,
|
|
178
|
+
Accept: 'application/vnd.github.v3+json',
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
throw e;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
157
187
|
updateAssigneeList = async (
|
|
158
188
|
issue: Pick<Issue, 'org' | 'repo' | 'number'>,
|
|
159
189
|
assigneeList: Member['name'][],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FieldOption, Project } from '../../domain/entities/Project';
|
|
2
2
|
import {
|
|
3
|
+
AGENT_FIELD_NAME,
|
|
3
4
|
DEPENDED_ISSUE_URL_FIELD_NAME,
|
|
4
5
|
NEXT_ACTION_DATE_FIELD_NAME,
|
|
5
6
|
NEXT_ACTION_HOUR_FIELD_NAME,
|
|
@@ -77,6 +78,10 @@ export const projectFromDefinition = (
|
|
|
77
78
|
const completionDate50PercentConfidence = definition.fields.find((field) =>
|
|
78
79
|
normalizeFieldName(field.name).startsWith('completiondate'),
|
|
79
80
|
);
|
|
81
|
+
const agentField = definition.fields.find(
|
|
82
|
+
(field) =>
|
|
83
|
+
normalizeFieldName(field.name) === normalizeFieldName(AGENT_FIELD_NAME),
|
|
84
|
+
);
|
|
80
85
|
return {
|
|
81
86
|
id: definition.id,
|
|
82
87
|
url: definition.url,
|
|
@@ -127,5 +132,12 @@ export const projectFromDefinition = (
|
|
|
127
132
|
fieldId: completionDate50PercentConfidence.fieldId,
|
|
128
133
|
}
|
|
129
134
|
: null,
|
|
135
|
+
agent: agentField
|
|
136
|
+
? {
|
|
137
|
+
name: agentField.name,
|
|
138
|
+
fieldId: agentField.fieldId,
|
|
139
|
+
options: agentField.options,
|
|
140
|
+
}
|
|
141
|
+
: null,
|
|
130
142
|
};
|
|
131
143
|
};
|
|
@@ -40,6 +40,7 @@ export const NEXT_ACTION_DATE_FIELD_NAME = 'Next Action Date';
|
|
|
40
40
|
export const NEXT_ACTION_HOUR_FIELD_NAME = 'Next Action Hour';
|
|
41
41
|
export const DEPENDED_ISSUE_URL_FIELD_NAME =
|
|
42
42
|
'Depended Issue URL separated by comma';
|
|
43
|
+
export const AGENT_FIELD_NAME = 'Agent';
|
|
43
44
|
|
|
44
45
|
export const REQUIRED_PROJECT_FIELDS: RequiredProjectFieldDefinition[] = [
|
|
45
46
|
{
|
|
@@ -62,4 +63,9 @@ export const REQUIRED_PROJECT_FIELDS: RequiredProjectFieldDefinition[] = [
|
|
|
62
63
|
dataType: 'TEXT',
|
|
63
64
|
options: [],
|
|
64
65
|
},
|
|
66
|
+
{
|
|
67
|
+
name: AGENT_FIELD_NAME,
|
|
68
|
+
dataType: 'SINGLE_SELECT',
|
|
69
|
+
options: [],
|
|
70
|
+
},
|
|
65
71
|
];
|
|
@@ -41,6 +41,7 @@ describe('GetStoryObjectMapUseCase', () => {
|
|
|
41
41
|
remainingEstimationMinutes: null,
|
|
42
42
|
dependedIssueUrlSeparatedByComma: null,
|
|
43
43
|
completionDate50PercentConfidence: null,
|
|
44
|
+
agent: null,
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
const createBasicIssue = (overrides: Partial<Issue> = {}): Issue => ({
|
|
@@ -68,6 +69,7 @@ describe('GetStoryObjectMapUseCase', () => {
|
|
|
68
69
|
createdAt: new Date(),
|
|
69
70
|
author: '',
|
|
70
71
|
closingIssueReferenceUrls: [],
|
|
72
|
+
agent: null,
|
|
71
73
|
...overrides,
|
|
72
74
|
});
|
|
73
75
|
|
|
@@ -160,6 +160,7 @@ export class HandleScheduledEventUseCase {
|
|
|
160
160
|
changeTargetPathAliases?: Record<string, string> | null;
|
|
161
161
|
allowedIssueAuthors?: string[] | null;
|
|
162
162
|
autoAssignManagerAuthors?: string[] | null;
|
|
163
|
+
agents?: string[] | null;
|
|
163
164
|
startPreparation?: {
|
|
164
165
|
defaultAgentName: string;
|
|
165
166
|
defaultLlmModelName?: string | null;
|
|
@@ -195,6 +196,7 @@ export class HandleScheduledEventUseCase {
|
|
|
195
196
|
}
|
|
196
197
|
await this.projectRequiredFieldCreateUseCase.run({
|
|
197
198
|
projectUrl: input.projectUrl,
|
|
199
|
+
agents: input.agents ?? null,
|
|
198
200
|
});
|
|
199
201
|
await this.setupTowerDefenceProjectUseCase.run({
|
|
200
202
|
projectUrl: input.projectUrl,
|