github-issue-tower-defence-management 1.104.1 → 1.104.2
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 +7 -0
- package/bin/domain/usecases/IssueRejectionEvaluator.js +21 -2
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +32 -1
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +182 -0
- package/src/domain/usecases/IssueRejectionEvaluator.ts +35 -1
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +194 -63
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +37 -0
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts +5 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts +1 -0
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.104.2](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.104.1...v1.104.2) (2026-06-26)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Performance Improvements
|
|
5
|
+
|
|
6
|
+
* **schedule:** derive review-readiness related open PRs in memory ([#989](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/989)) ([63dba8b](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/63dba8b2ddf0b813873337e0e4292e51493383fb)), closes [#988](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/988)
|
|
7
|
+
|
|
1
8
|
## [1.104.1](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.104.0...v1.104.1) (2026-06-26)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -4,7 +4,7 @@ exports.IssueRejectionEvaluator = void 0;
|
|
|
4
4
|
class IssueRejectionEvaluator {
|
|
5
5
|
constructor(issueRepository) {
|
|
6
6
|
this.issueRepository = issueRepository;
|
|
7
|
-
this.evaluate = async (issue, labelsAsLlmAgentName = []) => {
|
|
7
|
+
this.evaluate = async (issue, labelsAsLlmAgentName = [], options = {}) => {
|
|
8
8
|
const rejections = [];
|
|
9
9
|
let approvedPrUrl = null;
|
|
10
10
|
const categoryLabels = issue.labels.filter((label) => label.startsWith('category:'));
|
|
@@ -15,7 +15,9 @@ class IssueRejectionEvaluator {
|
|
|
15
15
|
(categoryLabels.length <= 0 || categoryLabels.includes('category:e2e'))) {
|
|
16
16
|
const prsToCheck = issue.isPr
|
|
17
17
|
? await this.resolveOpenPrsForPrItem(issue.url)
|
|
18
|
-
:
|
|
18
|
+
: options.relatedOpenPrUrls != null
|
|
19
|
+
? await this.resolveOpenPrsFromUrls(options.relatedOpenPrUrls)
|
|
20
|
+
: await this.issueRepository.findRelatedOpenPRs(issue.url);
|
|
19
21
|
if (prsToCheck.length <= 0) {
|
|
20
22
|
rejections.push({
|
|
21
23
|
type: 'PULL_REQUEST_NOT_FOUND',
|
|
@@ -100,6 +102,23 @@ class IssueRejectionEvaluator {
|
|
|
100
102
|
}
|
|
101
103
|
return [pr];
|
|
102
104
|
};
|
|
105
|
+
// Resolves the status of each prebuilt related PR URL via getOpenPullRequest,
|
|
106
|
+
// dropping any URL whose PR is not open (getOpenPullRequest returns null).
|
|
107
|
+
// This mirrors findRelatedOpenPRs, which also returns only OPEN pull requests,
|
|
108
|
+
// so the resulting set is equivalent while avoiding the per-issue timeline
|
|
109
|
+
// query. Duplicate URLs are de-duplicated to mirror findRelatedOpenPRs, which
|
|
110
|
+
// collapses duplicates via its internal Map keyed by PR URL.
|
|
111
|
+
this.resolveOpenPrsFromUrls = async (prUrls) => {
|
|
112
|
+
const uniquePrUrls = Array.from(new Set(prUrls));
|
|
113
|
+
const resolvedPrs = [];
|
|
114
|
+
for (const prUrl of uniquePrUrls) {
|
|
115
|
+
const pr = await this.issueRepository.getOpenPullRequest(prUrl);
|
|
116
|
+
if (pr !== null) {
|
|
117
|
+
resolvedPrs.push(pr);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return resolvedPrs;
|
|
121
|
+
};
|
|
103
122
|
this.extractChangeTargetMustPaths = (labels) => {
|
|
104
123
|
const prefix = 'change-target-must:';
|
|
105
124
|
const paths = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IssueRejectionEvaluator.js","sourceRoot":"","sources":["../../../src/domain/usecases/IssueRejectionEvaluator.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"IssueRejectionEvaluator.js","sourceRoot":"","sources":["../../../src/domain/usecases/IssueRejectionEvaluator.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,6 +34,7 @@ class RevertNotReadyReviewQueueIssueUseCase {
|
|
|
34
34
|
}
|
|
35
35
|
const { issues } = await this.issueRepository.getAllIssues(projectId, params.allowIssueCacheMinutes);
|
|
36
36
|
const awaitingQualityCheckIssues = issues.filter((issue) => issue.status === WorkflowStatus_1.AWAITING_QUALITY_CHECK_STATUS_NAME);
|
|
37
|
+
const relatedOpenPrUrlsByIssueUrl = this.buildRelatedOpenPrUrlsByIssueUrl(issues);
|
|
37
38
|
for (const issue of awaitingQualityCheckIssues) {
|
|
38
39
|
const hasLlmAgentLabel = issue.labels.some((l) => l === 'llm-agent' || l.startsWith('llm-agent:'));
|
|
39
40
|
if (hasLlmAgentLabel) {
|
|
@@ -42,7 +43,9 @@ class RevertNotReadyReviewQueueIssueUseCase {
|
|
|
42
43
|
if (!isAuthorAuthorizedForAutoStatusCheck(issue.author, allowedIssueAuthors)) {
|
|
43
44
|
continue;
|
|
44
45
|
}
|
|
45
|
-
const { rejections, approvedPrUrl } = await this.issueRejectionEvaluator.evaluate(issue, params.labelsAsLlmAgentName ?? []
|
|
46
|
+
const { rejections, approvedPrUrl } = await this.issueRejectionEvaluator.evaluate(issue, params.labelsAsLlmAgentName ?? [], {
|
|
47
|
+
relatedOpenPrUrls: relatedOpenPrUrlsByIssueUrl.get(issue.url) ?? [],
|
|
48
|
+
});
|
|
46
49
|
if (rejections.length > 0) {
|
|
47
50
|
await this.issueRepository.updateStatus(project, issue, awaitingWorkspaceStatusOption.id);
|
|
48
51
|
await this.issueCommentRepository.createComment(issue, `Auto Status Check: REJECTED\n${rejections.map((r) => `- ${r.detail}`).join('\n')}`);
|
|
@@ -70,6 +73,34 @@ class RevertNotReadyReviewQueueIssueUseCase {
|
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
};
|
|
76
|
+
// Derives, for each issue, the set of open pull request URLs that reference it
|
|
77
|
+
// via a closing keyword. The linkage is taken from each open PR item's
|
|
78
|
+
// closingIssueReferenceUrls (populated in bulk by fetchProjectItems), the same
|
|
79
|
+
// in-memory derivation SetDependedIssueUrlForOpenTaskPRsUseCase uses. This
|
|
80
|
+
// replaces the per-issue findRelatedOpenPRs timeline query in the
|
|
81
|
+
// review-readiness sweep with a single in-memory pass over the bulk items.
|
|
82
|
+
this.buildRelatedOpenPrUrlsByIssueUrl = (issues) => {
|
|
83
|
+
const openPrUrlsByIssueUrl = new Map();
|
|
84
|
+
for (const issue of issues) {
|
|
85
|
+
if (!issue.isPr || issue.isClosed) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
for (const referencedIssueUrl of issue.closingIssueReferenceUrls) {
|
|
89
|
+
const existing = openPrUrlsByIssueUrl.get(referencedIssueUrl);
|
|
90
|
+
if (existing) {
|
|
91
|
+
existing.add(issue.url);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
openPrUrlsByIssueUrl.set(referencedIssueUrl, new Set([issue.url]));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const result = new Map();
|
|
99
|
+
for (const [issueUrl, prUrls] of openPrUrlsByIssueUrl) {
|
|
100
|
+
result.set(issueUrl, Array.from(prUrls));
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
};
|
|
73
104
|
this.issueRejectionEvaluator = new IssueRejectionEvaluator_1.IssueRejectionEvaluator(issueRepository);
|
|
74
105
|
this.changeTargetPullRequestApprover = new ChangeTargetPullRequestApprover_1.ChangeTargetPullRequestApprover(issueRepository);
|
|
75
106
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RevertNotReadyReviewQueueIssueUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"RevertNotReadyReviewQueueIssueUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -205,6 +205,188 @@ describe('IssueRejectionEvaluator', () => {
|
|
|
205
205
|
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
+
describe('prebuilt relatedOpenPrUrls (in-memory derivation path)', () => {
|
|
209
|
+
it('should resolve PR status via getOpenPullRequest and not call findRelatedOpenPRs when relatedOpenPrUrls is provided', async () => {
|
|
210
|
+
mockIssueRepository.getOpenPullRequest.mockResolvedValue(
|
|
211
|
+
createReadyPr('https://github.com/user/repo/pull/7'),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const result = await evaluator.evaluate(
|
|
215
|
+
{
|
|
216
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
217
|
+
labels: [],
|
|
218
|
+
isPr: false,
|
|
219
|
+
},
|
|
220
|
+
[],
|
|
221
|
+
{ relatedOpenPrUrls: ['https://github.com/user/repo/pull/7'] },
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
225
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
|
|
226
|
+
'https://github.com/user/repo/pull/7',
|
|
227
|
+
);
|
|
228
|
+
expect(result.rejections).toHaveLength(0);
|
|
229
|
+
expect(result.approvedPrUrl).toBe(
|
|
230
|
+
'https://github.com/user/repo/pull/7',
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should reject with PULL_REQUEST_NOT_FOUND when relatedOpenPrUrls is empty', async () => {
|
|
235
|
+
const result = await evaluator.evaluate(
|
|
236
|
+
{
|
|
237
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
238
|
+
labels: [],
|
|
239
|
+
isPr: false,
|
|
240
|
+
},
|
|
241
|
+
[],
|
|
242
|
+
{ relatedOpenPrUrls: [] },
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
246
|
+
expect(mockIssueRepository.getOpenPullRequest).not.toHaveBeenCalled();
|
|
247
|
+
expect(result.rejections).toHaveLength(1);
|
|
248
|
+
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
249
|
+
expect(result.approvedPrUrl).toBeNull();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should drop a related URL whose PR is no longer open (getOpenPullRequest returns null), yielding PULL_REQUEST_NOT_FOUND', async () => {
|
|
253
|
+
mockIssueRepository.getOpenPullRequest.mockResolvedValue(null);
|
|
254
|
+
|
|
255
|
+
const result = await evaluator.evaluate(
|
|
256
|
+
{
|
|
257
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
258
|
+
labels: [],
|
|
259
|
+
isPr: false,
|
|
260
|
+
},
|
|
261
|
+
[],
|
|
262
|
+
{ relatedOpenPrUrls: ['https://github.com/user/repo/pull/9'] },
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
266
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
|
|
267
|
+
'https://github.com/user/repo/pull/9',
|
|
268
|
+
);
|
|
269
|
+
expect(result.rejections).toHaveLength(1);
|
|
270
|
+
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should reject with MULTIPLE_PULL_REQUESTS_FOUND when more than one related open PR resolves', async () => {
|
|
274
|
+
mockIssueRepository.getOpenPullRequest.mockImplementation(
|
|
275
|
+
(prUrl: string) => Promise.resolve(createReadyPr(prUrl)),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const result = await evaluator.evaluate(
|
|
279
|
+
{
|
|
280
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
281
|
+
labels: [],
|
|
282
|
+
isPr: false,
|
|
283
|
+
},
|
|
284
|
+
[],
|
|
285
|
+
{
|
|
286
|
+
relatedOpenPrUrls: [
|
|
287
|
+
'https://github.com/user/repo/pull/1',
|
|
288
|
+
'https://github.com/user/repo/pull/2',
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
294
|
+
expect(result.rejections).toHaveLength(1);
|
|
295
|
+
expect(result.rejections[0].type).toBe('MULTIPLE_PULL_REQUESTS_FOUND');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should de-duplicate repeated related URLs before resolving, mirroring findRelatedOpenPRs', async () => {
|
|
299
|
+
mockIssueRepository.getOpenPullRequest.mockResolvedValue(
|
|
300
|
+
createReadyPr('https://github.com/user/repo/pull/5'),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const result = await evaluator.evaluate(
|
|
304
|
+
{
|
|
305
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
306
|
+
labels: [],
|
|
307
|
+
isPr: false,
|
|
308
|
+
},
|
|
309
|
+
[],
|
|
310
|
+
{
|
|
311
|
+
relatedOpenPrUrls: [
|
|
312
|
+
'https://github.com/user/repo/pull/5',
|
|
313
|
+
'https://github.com/user/repo/pull/5',
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledTimes(1);
|
|
319
|
+
expect(result.rejections).toHaveLength(0);
|
|
320
|
+
expect(result.approvedPrUrl).toBe(
|
|
321
|
+
'https://github.com/user/repo/pull/5',
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should produce the same draft rejection through the prebuilt path as through findRelatedOpenPRs', async () => {
|
|
326
|
+
mockIssueRepository.getOpenPullRequest.mockResolvedValue(
|
|
327
|
+
createReadyPr('https://github.com/user/repo/pull/3', {
|
|
328
|
+
isDraft: true,
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const result = await evaluator.evaluate(
|
|
333
|
+
{
|
|
334
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
335
|
+
labels: [],
|
|
336
|
+
isPr: false,
|
|
337
|
+
},
|
|
338
|
+
[],
|
|
339
|
+
{ relatedOpenPrUrls: ['https://github.com/user/repo/pull/3'] },
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
expect(result.rejections).toHaveLength(1);
|
|
343
|
+
expect(result.rejections[0].type).toBe('PULL_REQUEST_IS_DRAFT');
|
|
344
|
+
expect(result.approvedPrUrl).toBeNull();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should still use findRelatedOpenPRs when relatedOpenPrUrls is undefined (single-issue path unchanged)', async () => {
|
|
348
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
349
|
+
createReadyPr(),
|
|
350
|
+
]);
|
|
351
|
+
|
|
352
|
+
const result = await evaluator.evaluate({
|
|
353
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
354
|
+
labels: [],
|
|
355
|
+
isPr: false,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalledWith(
|
|
359
|
+
'https://github.com/user/repo/issues/1',
|
|
360
|
+
);
|
|
361
|
+
expect(result.rejections).toHaveLength(0);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should ignore relatedOpenPrUrls for a PR item (isPr=true) and use getOpenPullRequest on the PR URL itself', async () => {
|
|
365
|
+
mockIssueRepository.getOpenPullRequest.mockResolvedValue(
|
|
366
|
+
createReadyPr('https://github.com/user/repo/pull/10'),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const result = await evaluator.evaluate(
|
|
370
|
+
{
|
|
371
|
+
url: 'https://github.com/user/repo/pull/10',
|
|
372
|
+
labels: [],
|
|
373
|
+
isPr: true,
|
|
374
|
+
},
|
|
375
|
+
[],
|
|
376
|
+
{ relatedOpenPrUrls: ['https://github.com/user/repo/pull/999'] },
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
380
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
|
|
381
|
+
'https://github.com/user/repo/pull/10',
|
|
382
|
+
);
|
|
383
|
+
expect(mockIssueRepository.getOpenPullRequest).not.toHaveBeenCalledWith(
|
|
384
|
+
'https://github.com/user/repo/pull/999',
|
|
385
|
+
);
|
|
386
|
+
expect(result.rejections).toHaveLength(0);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
208
390
|
describe('change-target-must: label behavior', () => {
|
|
209
391
|
const prUrl = 'https://github.com/user/repo/pull/1';
|
|
210
392
|
|
|
@@ -18,6 +18,17 @@ export type PrRejectionResult = {
|
|
|
18
18
|
approvedPrUrl: string | null;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export type EvaluateOptions = {
|
|
22
|
+
// When provided, the related open pull requests for a non-PR issue are
|
|
23
|
+
// resolved from these prebuilt URLs via getOpenPullRequest instead of
|
|
24
|
+
// discovering them with a per-issue findRelatedOpenPRs timeline query.
|
|
25
|
+
// Callers that already hold the bulk project items (e.g. the per-cycle
|
|
26
|
+
// review-readiness sweep) derive these URLs in memory from PR items'
|
|
27
|
+
// closingIssueReferenceUrls, eliminating the per-issue timeline fan-out.
|
|
28
|
+
// When null or undefined, behaviour is unchanged: findRelatedOpenPRs runs.
|
|
29
|
+
relatedOpenPrUrls?: string[] | null;
|
|
30
|
+
};
|
|
31
|
+
|
|
21
32
|
export class IssueRejectionEvaluator {
|
|
22
33
|
constructor(
|
|
23
34
|
private readonly issueRepository: Pick<
|
|
@@ -36,6 +47,7 @@ export class IssueRejectionEvaluator {
|
|
|
36
47
|
isPr: boolean;
|
|
37
48
|
},
|
|
38
49
|
labelsAsLlmAgentName: string[] = [],
|
|
50
|
+
options: EvaluateOptions = {},
|
|
39
51
|
): Promise<PrRejectionResult> => {
|
|
40
52
|
const rejections: { type: PrRejectedReasonType; detail: string }[] = [];
|
|
41
53
|
let approvedPrUrl: string | null = null;
|
|
@@ -57,7 +69,9 @@ export class IssueRejectionEvaluator {
|
|
|
57
69
|
) {
|
|
58
70
|
const prsToCheck = issue.isPr
|
|
59
71
|
? await this.resolveOpenPrsForPrItem(issue.url)
|
|
60
|
-
:
|
|
72
|
+
: options.relatedOpenPrUrls != null
|
|
73
|
+
? await this.resolveOpenPrsFromUrls(options.relatedOpenPrUrls)
|
|
74
|
+
: await this.issueRepository.findRelatedOpenPRs(issue.url);
|
|
61
75
|
|
|
62
76
|
if (prsToCheck.length <= 0) {
|
|
63
77
|
rejections.push({
|
|
@@ -160,6 +174,26 @@ export class IssueRejectionEvaluator {
|
|
|
160
174
|
return [pr];
|
|
161
175
|
};
|
|
162
176
|
|
|
177
|
+
// Resolves the status of each prebuilt related PR URL via getOpenPullRequest,
|
|
178
|
+
// dropping any URL whose PR is not open (getOpenPullRequest returns null).
|
|
179
|
+
// This mirrors findRelatedOpenPRs, which also returns only OPEN pull requests,
|
|
180
|
+
// so the resulting set is equivalent while avoiding the per-issue timeline
|
|
181
|
+
// query. Duplicate URLs are de-duplicated to mirror findRelatedOpenPRs, which
|
|
182
|
+
// collapses duplicates via its internal Map keyed by PR URL.
|
|
183
|
+
private resolveOpenPrsFromUrls = async (
|
|
184
|
+
prUrls: string[],
|
|
185
|
+
): Promise<RelatedPullRequest[]> => {
|
|
186
|
+
const uniquePrUrls = Array.from(new Set(prUrls));
|
|
187
|
+
const resolvedPrs: RelatedPullRequest[] = [];
|
|
188
|
+
for (const prUrl of uniquePrUrls) {
|
|
189
|
+
const pr = await this.issueRepository.getOpenPullRequest(prUrl);
|
|
190
|
+
if (pr !== null) {
|
|
191
|
+
resolvedPrs.push(pr);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return resolvedPrs;
|
|
195
|
+
};
|
|
196
|
+
|
|
163
197
|
private extractChangeTargetMustPaths = (labels: string[]): string[] => {
|
|
164
198
|
const prefix = 'change-target-must:';
|
|
165
199
|
const paths: string[] = [];
|
|
@@ -93,7 +93,20 @@ const createMockPullRequest = (overrides: Partial<Issue> = {}): Issue =>
|
|
|
93
93
|
...overrides,
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
type RelatedPrLike = {
|
|
97
|
+
url: string;
|
|
98
|
+
isConflicted: boolean;
|
|
99
|
+
isPassedAllCiJob: boolean;
|
|
100
|
+
isCiStateSuccess: boolean;
|
|
101
|
+
isResolvedAllReviewComments: boolean;
|
|
102
|
+
isBranchOutOfDate: boolean;
|
|
103
|
+
missingRequiredCheckNames: string[];
|
|
104
|
+
isDraft?: boolean;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const createReadyPr = (
|
|
108
|
+
url = 'https://github.com/user/repo/pull/1',
|
|
109
|
+
): RelatedPrLike => ({
|
|
97
110
|
url,
|
|
98
111
|
isConflicted: false,
|
|
99
112
|
isPassedAllCiJob: true,
|
|
@@ -103,6 +116,37 @@ const createReadyPr = (url = 'https://github.com/user/repo/pull/1') => ({
|
|
|
103
116
|
missingRequiredCheckNames: [],
|
|
104
117
|
});
|
|
105
118
|
|
|
119
|
+
// Builds the in-memory linkage the production code now relies on instead of the
|
|
120
|
+
// per-issue findRelatedOpenPRs timeline query: each related pull request becomes
|
|
121
|
+
// an open PR project item whose closingIssueReferenceUrls points at the AQC
|
|
122
|
+
// issue, and getOpenPullRequest is routed by URL to return that PR's status.
|
|
123
|
+
// This proves the review-readiness outcome is identical to the previous
|
|
124
|
+
// findRelatedOpenPRs-based path while removing the per-issue timeline call.
|
|
125
|
+
const linkRelatedOpenPrsToIssue = (
|
|
126
|
+
mockIssueRepository: {
|
|
127
|
+
getAllIssues: jest.Mock;
|
|
128
|
+
getOpenPullRequest: jest.Mock;
|
|
129
|
+
},
|
|
130
|
+
issue: Issue,
|
|
131
|
+
relatedPrs: RelatedPrLike[],
|
|
132
|
+
): void => {
|
|
133
|
+
const prItems = relatedPrs.map((pr, index) =>
|
|
134
|
+
createMockPullRequest({
|
|
135
|
+
url: pr.url,
|
|
136
|
+
number: 1000 + index,
|
|
137
|
+
closingIssueReferenceUrls: [issue.url],
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
141
|
+
issues: [issue, ...prItems],
|
|
142
|
+
cacheUsed: false,
|
|
143
|
+
});
|
|
144
|
+
const prByUrl = new Map(relatedPrs.map((pr) => [pr.url, pr]));
|
|
145
|
+
mockIssueRepository.getOpenPullRequest.mockImplementation((prUrl: string) =>
|
|
146
|
+
Promise.resolve(prByUrl.get(prUrl) ?? null),
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
106
150
|
describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
107
151
|
let mockProjectRepository: {
|
|
108
152
|
findProjectIdByUrl: jest.Mock;
|
|
@@ -206,7 +250,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
206
250
|
issues: [issue],
|
|
207
251
|
cacheUsed: false,
|
|
208
252
|
});
|
|
209
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
210
253
|
|
|
211
254
|
await useCase.run({
|
|
212
255
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -238,7 +281,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
238
281
|
issues: [issue],
|
|
239
282
|
cacheUsed: false,
|
|
240
283
|
});
|
|
241
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
242
284
|
|
|
243
285
|
await useCase.run({
|
|
244
286
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -260,7 +302,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
260
302
|
issues: [issue],
|
|
261
303
|
cacheUsed: false,
|
|
262
304
|
});
|
|
263
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
264
305
|
|
|
265
306
|
await useCase.run({
|
|
266
307
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -282,7 +323,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
282
323
|
issues: [issue],
|
|
283
324
|
cacheUsed: false,
|
|
284
325
|
});
|
|
285
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
286
326
|
|
|
287
327
|
await useCase.run({
|
|
288
328
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -310,7 +350,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
310
350
|
issues: [issue],
|
|
311
351
|
cacheUsed: false,
|
|
312
352
|
});
|
|
313
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
314
353
|
|
|
315
354
|
await useCase.run({
|
|
316
355
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -330,13 +369,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
330
369
|
const issue = createMockIssue({
|
|
331
370
|
status: 'Awaiting Quality Check',
|
|
332
371
|
});
|
|
333
|
-
mockIssueRepository
|
|
334
|
-
issues: [issue],
|
|
335
|
-
cacheUsed: false,
|
|
336
|
-
});
|
|
337
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
338
|
-
createReadyPr(),
|
|
339
|
-
]);
|
|
372
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [createReadyPr()]);
|
|
340
373
|
|
|
341
374
|
await useCase.run({
|
|
342
375
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -352,11 +385,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
352
385
|
const issue = createMockIssue({
|
|
353
386
|
status: 'Awaiting Quality Check',
|
|
354
387
|
});
|
|
355
|
-
mockIssueRepository
|
|
356
|
-
issues: [issue],
|
|
357
|
-
cacheUsed: false,
|
|
358
|
-
});
|
|
359
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
388
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
|
|
360
389
|
{ ...createReadyPr(), isConflicted: true },
|
|
361
390
|
]);
|
|
362
391
|
|
|
@@ -385,11 +414,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
385
414
|
const issue = createMockIssue({
|
|
386
415
|
status: 'Awaiting Quality Check',
|
|
387
416
|
});
|
|
388
|
-
mockIssueRepository
|
|
389
|
-
issues: [issue],
|
|
390
|
-
cacheUsed: false,
|
|
391
|
-
});
|
|
392
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
417
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
|
|
393
418
|
{
|
|
394
419
|
...createReadyPr(),
|
|
395
420
|
isPassedAllCiJob: false,
|
|
@@ -422,11 +447,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
422
447
|
const issue = createMockIssue({
|
|
423
448
|
status: 'Awaiting Quality Check',
|
|
424
449
|
});
|
|
425
|
-
mockIssueRepository
|
|
426
|
-
issues: [issue],
|
|
427
|
-
cacheUsed: false,
|
|
428
|
-
});
|
|
429
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
450
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
|
|
430
451
|
{ ...createReadyPr(), isResolvedAllReviewComments: false },
|
|
431
452
|
]);
|
|
432
453
|
|
|
@@ -455,11 +476,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
455
476
|
const issue = createMockIssue({
|
|
456
477
|
status: 'Awaiting Quality Check',
|
|
457
478
|
});
|
|
458
|
-
mockIssueRepository
|
|
459
|
-
issues: [issue],
|
|
460
|
-
cacheUsed: false,
|
|
461
|
-
});
|
|
462
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
479
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
|
|
463
480
|
{ ...createReadyPr(), isDraft: true },
|
|
464
481
|
]);
|
|
465
482
|
|
|
@@ -488,11 +505,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
488
505
|
const issue = createMockIssue({
|
|
489
506
|
status: 'Awaiting Quality Check',
|
|
490
507
|
});
|
|
491
|
-
mockIssueRepository
|
|
492
|
-
issues: [issue],
|
|
493
|
-
cacheUsed: false,
|
|
494
|
-
});
|
|
495
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
508
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
|
|
496
509
|
createReadyPr('https://github.com/user/repo/pull/1'),
|
|
497
510
|
createReadyPr('https://github.com/user/repo/pull/2'),
|
|
498
511
|
]);
|
|
@@ -522,11 +535,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
522
535
|
const issue = createMockIssue({
|
|
523
536
|
status: 'Awaiting Quality Check',
|
|
524
537
|
});
|
|
525
|
-
mockIssueRepository
|
|
526
|
-
issues: [issue],
|
|
527
|
-
cacheUsed: false,
|
|
528
|
-
});
|
|
529
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
538
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
|
|
530
539
|
{
|
|
531
540
|
...createReadyPr(),
|
|
532
541
|
isPassedAllCiJob: false,
|
|
@@ -556,17 +565,151 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
556
565
|
);
|
|
557
566
|
});
|
|
558
567
|
|
|
559
|
-
describe('
|
|
560
|
-
|
|
568
|
+
describe('in-memory related-PR derivation (no per-issue timeline call)', () => {
|
|
569
|
+
it('should never call findRelatedOpenPRs while sweeping Awaiting Quality Check issues', async () => {
|
|
570
|
+
const readyIssue = createMockIssue({
|
|
571
|
+
status: 'Awaiting Quality Check',
|
|
572
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
573
|
+
});
|
|
574
|
+
const notReadyIssue = createMockIssue({
|
|
575
|
+
status: 'Awaiting Quality Check',
|
|
576
|
+
number: 2,
|
|
577
|
+
url: 'https://github.com/user/repo/issues/2',
|
|
578
|
+
});
|
|
579
|
+
const readyPrItem = createMockPullRequest({
|
|
580
|
+
status: 'In Progress',
|
|
581
|
+
url: 'https://github.com/user/repo/pull/100',
|
|
582
|
+
number: 100,
|
|
583
|
+
closingIssueReferenceUrls: ['https://github.com/user/repo/issues/1'],
|
|
584
|
+
});
|
|
585
|
+
const conflictedPrItem = createMockPullRequest({
|
|
586
|
+
status: 'In Progress',
|
|
587
|
+
url: 'https://github.com/user/repo/pull/200',
|
|
588
|
+
number: 200,
|
|
589
|
+
closingIssueReferenceUrls: ['https://github.com/user/repo/issues/2'],
|
|
590
|
+
});
|
|
591
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
592
|
+
issues: [readyIssue, notReadyIssue, readyPrItem, conflictedPrItem],
|
|
593
|
+
cacheUsed: false,
|
|
594
|
+
});
|
|
595
|
+
mockIssueRepository.getOpenPullRequest.mockImplementation(
|
|
596
|
+
(prUrl: string) => {
|
|
597
|
+
if (prUrl === 'https://github.com/user/repo/pull/100') {
|
|
598
|
+
return Promise.resolve(createReadyPr(prUrl));
|
|
599
|
+
}
|
|
600
|
+
if (prUrl === 'https://github.com/user/repo/pull/200') {
|
|
601
|
+
return Promise.resolve({
|
|
602
|
+
...createReadyPr(prUrl),
|
|
603
|
+
isConflicted: true,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return Promise.resolve(null);
|
|
607
|
+
},
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
await useCase.run({
|
|
611
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
612
|
+
allowIssueCacheMinutes: 10,
|
|
613
|
+
allowedIssueAuthors: ['owner'],
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
617
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
|
|
618
|
+
'https://github.com/user/repo/pull/100',
|
|
619
|
+
);
|
|
620
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
|
|
621
|
+
'https://github.com/user/repo/pull/200',
|
|
622
|
+
);
|
|
623
|
+
expect(mockIssueRepository.updateStatus).toHaveBeenCalledTimes(1);
|
|
624
|
+
expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
|
|
625
|
+
mockProject,
|
|
626
|
+
notReadyIssue,
|
|
627
|
+
'awaiting-workspace-id',
|
|
628
|
+
);
|
|
629
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
630
|
+
notReadyIssue,
|
|
631
|
+
expect.stringContaining('PULL_REQUEST_CONFLICTED'),
|
|
632
|
+
);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it('should match a PR to its issue via closingIssueReferenceUrls even when the PR item is listed before the issue', async () => {
|
|
561
636
|
const issue = createMockIssue({
|
|
562
637
|
status: 'Awaiting Quality Check',
|
|
563
|
-
|
|
638
|
+
url: 'https://github.com/user/repo/issues/42',
|
|
639
|
+
number: 42,
|
|
640
|
+
});
|
|
641
|
+
const prItem = createMockPullRequest({
|
|
642
|
+
status: 'In Progress',
|
|
643
|
+
url: 'https://github.com/user/repo/pull/77',
|
|
644
|
+
number: 77,
|
|
645
|
+
closingIssueReferenceUrls: ['https://github.com/user/repo/issues/42'],
|
|
646
|
+
});
|
|
647
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
648
|
+
issues: [prItem, issue],
|
|
649
|
+
cacheUsed: false,
|
|
650
|
+
});
|
|
651
|
+
mockIssueRepository.getOpenPullRequest.mockResolvedValue(
|
|
652
|
+
createReadyPr('https://github.com/user/repo/pull/77'),
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
await useCase.run({
|
|
656
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
657
|
+
allowIssueCacheMinutes: 10,
|
|
658
|
+
allowedIssueAuthors: ['owner'],
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
|
|
662
|
+
'https://github.com/user/repo/pull/77',
|
|
663
|
+
);
|
|
664
|
+
expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
|
|
665
|
+
expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it('should not match a closed PR even when its closingIssueReferenceUrls points at the issue', async () => {
|
|
669
|
+
const issue = createMockIssue({
|
|
670
|
+
status: 'Awaiting Quality Check',
|
|
671
|
+
url: 'https://github.com/user/repo/issues/9',
|
|
672
|
+
number: 9,
|
|
673
|
+
});
|
|
674
|
+
const closedPrItem = createMockPullRequest({
|
|
675
|
+
status: 'In Progress',
|
|
676
|
+
url: 'https://github.com/user/repo/pull/9',
|
|
677
|
+
number: 9,
|
|
678
|
+
isClosed: true,
|
|
679
|
+
state: 'CLOSED',
|
|
680
|
+
closingIssueReferenceUrls: ['https://github.com/user/repo/issues/9'],
|
|
564
681
|
});
|
|
565
682
|
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
566
|
-
issues: [issue],
|
|
683
|
+
issues: [issue, closedPrItem],
|
|
567
684
|
cacheUsed: false,
|
|
568
685
|
});
|
|
569
|
-
|
|
686
|
+
|
|
687
|
+
await useCase.run({
|
|
688
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
689
|
+
allowIssueCacheMinutes: 10,
|
|
690
|
+
allowedIssueAuthors: ['owner'],
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
expect(mockIssueRepository.getOpenPullRequest).not.toHaveBeenCalled();
|
|
694
|
+
expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
|
|
695
|
+
mockProject,
|
|
696
|
+
issue,
|
|
697
|
+
'awaiting-workspace-id',
|
|
698
|
+
);
|
|
699
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
700
|
+
issue,
|
|
701
|
+
expect.stringContaining('PULL_REQUEST_NOT_FOUND'),
|
|
702
|
+
);
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
describe('change-target label auto-approve', () => {
|
|
707
|
+
const setupReadyIssue = (labels: string[]) => {
|
|
708
|
+
const issue = createMockIssue({
|
|
709
|
+
status: 'Awaiting Quality Check',
|
|
710
|
+
labels,
|
|
711
|
+
});
|
|
712
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
|
|
570
713
|
createReadyPr(),
|
|
571
714
|
]);
|
|
572
715
|
return issue;
|
|
@@ -699,11 +842,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
699
842
|
status: 'Awaiting Quality Check',
|
|
700
843
|
labels: ['change-target:src/domain'],
|
|
701
844
|
});
|
|
702
|
-
mockIssueRepository
|
|
703
|
-
issues: [issue],
|
|
704
|
-
cacheUsed: false,
|
|
705
|
-
});
|
|
706
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
845
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, []);
|
|
707
846
|
|
|
708
847
|
await runCycle();
|
|
709
848
|
|
|
@@ -723,11 +862,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
723
862
|
status: 'Awaiting Quality Check',
|
|
724
863
|
labels: ['change-target:src/domain'],
|
|
725
864
|
});
|
|
726
|
-
mockIssueRepository
|
|
727
|
-
issues: [issue],
|
|
728
|
-
cacheUsed: false,
|
|
729
|
-
});
|
|
730
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
865
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
|
|
731
866
|
{ ...createReadyPr(), isConflicted: true },
|
|
732
867
|
]);
|
|
733
868
|
|
|
@@ -749,11 +884,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
|
|
|
749
884
|
status: 'Awaiting Quality Check',
|
|
750
885
|
labels: ['llm-agent', 'change-target:src/domain'],
|
|
751
886
|
});
|
|
752
|
-
mockIssueRepository
|
|
753
|
-
issues: [issue],
|
|
754
|
-
cacheUsed: false,
|
|
755
|
-
});
|
|
756
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
887
|
+
linkRelatedOpenPrsToIssue(mockIssueRepository, issue, [
|
|
757
888
|
createReadyPr(),
|
|
758
889
|
]);
|
|
759
890
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Issue } from '../entities/Issue';
|
|
1
2
|
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
2
3
|
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
3
4
|
import { IssueCommentRepository } from './adapter-interfaces/IssueCommentRepository';
|
|
@@ -90,6 +91,9 @@ export class RevertNotReadyReviewQueueIssueUseCase {
|
|
|
90
91
|
(issue) => issue.status === AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
91
92
|
);
|
|
92
93
|
|
|
94
|
+
const relatedOpenPrUrlsByIssueUrl =
|
|
95
|
+
this.buildRelatedOpenPrUrlsByIssueUrl(issues);
|
|
96
|
+
|
|
93
97
|
for (const issue of awaitingQualityCheckIssues) {
|
|
94
98
|
const hasLlmAgentLabel = issue.labels.some(
|
|
95
99
|
(l) => l === 'llm-agent' || l.startsWith('llm-agent:'),
|
|
@@ -108,6 +112,9 @@ export class RevertNotReadyReviewQueueIssueUseCase {
|
|
|
108
112
|
await this.issueRejectionEvaluator.evaluate(
|
|
109
113
|
issue,
|
|
110
114
|
params.labelsAsLlmAgentName ?? [],
|
|
115
|
+
{
|
|
116
|
+
relatedOpenPrUrls: relatedOpenPrUrlsByIssueUrl.get(issue.url) ?? [],
|
|
117
|
+
},
|
|
111
118
|
);
|
|
112
119
|
if (rejections.length > 0) {
|
|
113
120
|
await this.issueRepository.updateStatus(
|
|
@@ -175,4 +182,34 @@ export class RevertNotReadyReviewQueueIssueUseCase {
|
|
|
175
182
|
}
|
|
176
183
|
}
|
|
177
184
|
};
|
|
185
|
+
|
|
186
|
+
// Derives, for each issue, the set of open pull request URLs that reference it
|
|
187
|
+
// via a closing keyword. The linkage is taken from each open PR item's
|
|
188
|
+
// closingIssueReferenceUrls (populated in bulk by fetchProjectItems), the same
|
|
189
|
+
// in-memory derivation SetDependedIssueUrlForOpenTaskPRsUseCase uses. This
|
|
190
|
+
// replaces the per-issue findRelatedOpenPRs timeline query in the
|
|
191
|
+
// review-readiness sweep with a single in-memory pass over the bulk items.
|
|
192
|
+
private buildRelatedOpenPrUrlsByIssueUrl = (
|
|
193
|
+
issues: Issue[],
|
|
194
|
+
): Map<string, string[]> => {
|
|
195
|
+
const openPrUrlsByIssueUrl = new Map<string, Set<string>>();
|
|
196
|
+
for (const issue of issues) {
|
|
197
|
+
if (!issue.isPr || issue.isClosed) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
for (const referencedIssueUrl of issue.closingIssueReferenceUrls) {
|
|
201
|
+
const existing = openPrUrlsByIssueUrl.get(referencedIssueUrl);
|
|
202
|
+
if (existing) {
|
|
203
|
+
existing.add(issue.url);
|
|
204
|
+
} else {
|
|
205
|
+
openPrUrlsByIssueUrl.set(referencedIssueUrl, new Set([issue.url]));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const result = new Map<string, string[]>();
|
|
210
|
+
for (const [issueUrl, prUrls] of openPrUrlsByIssueUrl) {
|
|
211
|
+
result.set(issueUrl, Array.from(prUrls));
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
};
|
|
178
215
|
}
|
|
@@ -7,6 +7,9 @@ export type PrRejectionResult = {
|
|
|
7
7
|
}[];
|
|
8
8
|
approvedPrUrl: string | null;
|
|
9
9
|
};
|
|
10
|
+
export type EvaluateOptions = {
|
|
11
|
+
relatedOpenPrUrls?: string[] | null;
|
|
12
|
+
};
|
|
10
13
|
export declare class IssueRejectionEvaluator {
|
|
11
14
|
private readonly issueRepository;
|
|
12
15
|
constructor(issueRepository: Pick<IssueRepository, 'findRelatedOpenPRs' | 'getOpenPullRequest' | 'getPullRequestChangedFilePaths' | 'requestChangesWithInlineComment'>);
|
|
@@ -14,8 +17,9 @@ export declare class IssueRejectionEvaluator {
|
|
|
14
17
|
url: string;
|
|
15
18
|
labels: string[];
|
|
16
19
|
isPr: boolean;
|
|
17
|
-
}, labelsAsLlmAgentName?: string[]) => Promise<PrRejectionResult>;
|
|
20
|
+
}, labelsAsLlmAgentName?: string[], options?: EvaluateOptions) => Promise<PrRejectionResult>;
|
|
18
21
|
private resolveOpenPrsForPrItem;
|
|
22
|
+
private resolveOpenPrsFromUrls;
|
|
19
23
|
private extractChangeTargetMustPaths;
|
|
20
24
|
private isFilePathUnderPath;
|
|
21
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IssueRejectionEvaluator.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/IssueRejectionEvaluator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAEhB,MAAM,sCAAsC,CAAC;AAE9C,MAAM,MAAM,oBAAoB,GAC5B,wBAAwB,GACxB,8BAA8B,GAC9B,uBAAuB,GACvB,yBAAyB,GACzB,kCAAkC,GAClC,+BAA+B,GAC/B,iCAAiC,GACjC,qCAAqC,CAAC;AAE1C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE;QAAE,IAAI,EAAE,oBAAoB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,eAAe;gBAAf,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,oBAAoB,GACpB,oBAAoB,GACpB,gCAAgC,GAChC,iCAAiC,CACpC;IAGH,QAAQ,GACN,OAAO;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,EAAE,OAAO,CAAC;KACf,EACD,uBAAsB,MAAM,EAAO,
|
|
1
|
+
{"version":3,"file":"IssueRejectionEvaluator.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/IssueRejectionEvaluator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAEhB,MAAM,sCAAsC,CAAC;AAE9C,MAAM,MAAM,oBAAoB,GAC5B,wBAAwB,GACxB,8BAA8B,GAC9B,uBAAuB,GACvB,yBAAyB,GACzB,kCAAkC,GAClC,+BAA+B,GAC/B,iCAAiC,GACjC,qCAAqC,CAAC;AAE1C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE;QAAE,IAAI,EAAE,oBAAoB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAQ5B,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACrC,CAAC;AAEF,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,eAAe;gBAAf,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,oBAAoB,GACpB,oBAAoB,GACpB,gCAAgC,GAChC,iCAAiC,CACpC;IAGH,QAAQ,GACN,OAAO;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,EAAE,OAAO,CAAC;KACf,EACD,uBAAsB,MAAM,EAAO,EACnC,UAAS,eAAoB,KAC5B,OAAO,CAAC,iBAAiB,CAAC,CAkH3B;IAEF,OAAO,CAAC,uBAAuB,CAQ7B;IAQF,OAAO,CAAC,sBAAsB,CAY5B;IAEF,OAAO,CAAC,4BAA4B,CAYlC;IAEF,OAAO,CAAC,mBAAmB,CAIwC;CACpE"}
|
|
@@ -15,5 +15,6 @@ export declare class RevertNotReadyReviewQueueIssueUseCase {
|
|
|
15
15
|
changeTargetPathAliases?: Record<string, string> | null;
|
|
16
16
|
allowedIssueAuthors?: string[] | null;
|
|
17
17
|
}) => Promise<void>;
|
|
18
|
+
private buildRelatedOpenPrUrlsByIssueUrl;
|
|
18
19
|
}
|
|
19
20
|
//# sourceMappingURL=RevertNotReadyReviewQueueIssueUseCase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RevertNotReadyReviewQueueIssueUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RevertNotReadyReviewQueueIssueUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AAsBrF,qBAAa,qCAAqC;IAK9C,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAWhC,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IAnBzC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA0B;IAClE,OAAO,CAAC,QAAQ,CAAC,+BAA+B,CAAkC;gBAG/D,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,oBAAoB,GAAG,YAAY,CACpC,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,cAAc,GACd,cAAc,GACd,aAAa,GACb,oBAAoB,GACpB,oBAAoB,GACpB,gCAAgC,GAChC,oBAAoB,GACpB,iCAAiC,CACpC,EACgB,sBAAsB,EAAE,IAAI,CAC3C,sBAAsB,EACtB,eAAe,CAChB;IAQH,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,oBAAoB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACvC,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;QACxD,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACvC,KAAG,OAAO,CAAC,IAAI,CAAC,CAyHf;IAQF,OAAO,CAAC,gCAAgC,CAsBtC;CACH"}
|