@gitkraken/core-gitlens 0.5.109 → 0.5.110
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 +33 -1
- package/dist/git/models/pullRequest.d.ts +81 -0
- package/dist/git/models/pullRequest.d.ts.map +1 -1
- package/dist/git/models/pullRequest.js +15 -0
- package/dist/git/models/pullRequest.js.map +1 -1
- package/dist/git/utils/pullRequest.utils.d.ts +11 -1
- package/dist/git/utils/pullRequest.utils.d.ts.map +1 -1
- package/dist/git/utils/pullRequest.utils.js +40 -0
- package/dist/git/utils/pullRequest.utils.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +37 -4
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +147 -16
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/api/pullRequestSearchQuery.d.ts +27 -1
- package/dist/plus/git-github/api/pullRequestSearchQuery.d.ts.map +1 -1
- package/dist/plus/git-github/api/pullRequestSearchQuery.js +73 -6
- package/dist/plus/git-github/api/pullRequestSearchQuery.js.map +1 -1
- package/dist/plus/git-github/models.d.ts +17 -5
- package/dist/plus/git-github/models.d.ts.map +1 -1
- package/dist/plus/git-github/models.js +14 -1
- package/dist/plus/git-github/models.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +16 -1
- package/dist/plus/integrations/integrationService.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.js +9 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +45 -5
- package/dist/plus/integrations/manager.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +12 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +18 -0
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +1 -1
- package/dist/plus/integrations/models/integration.d.ts.map +1 -1
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/providerFilters.d.ts +1 -1
- package/dist/plus/integrations/providerFilters.d.ts.map +1 -1
- package/dist/plus/integrations/providers/accounts.d.ts +6 -0
- package/dist/plus/integrations/providers/accounts.d.ts.map +1 -0
- package/dist/plus/integrations/providers/accounts.js +30 -0
- package/dist/plus/integrations/providers/accounts.js.map +1 -0
- package/dist/plus/integrations/providers/github.d.ts +9 -0
- package/dist/plus/integrations/providers/github.d.ts.map +1 -1
- package/dist/plus/integrations/providers/github.js +114 -135
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +9 -0
- package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
- package/dist/plus/integrations/providers/gitlab.js +23 -4
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +14 -32
- package/dist/plus/integrations/providers/models.d.ts.map +1 -1
- package/dist/plus/integrations/providers/models.js +36 -105
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/pullRequestReviews.d.ts +50 -0
- package/dist/plus/integrations/providers/pullRequestReviews.d.ts.map +1 -0
- package/dist/plus/integrations/providers/pullRequestReviews.js +99 -0
- package/dist/plus/integrations/providers/pullRequestReviews.js.map +1 -0
- package/dist/plus/integrations/reads/counts.d.ts +55 -0
- package/dist/plus/integrations/reads/counts.d.ts.map +1 -1
- package/dist/plus/integrations/reads/counts.js +146 -2
- package/dist/plus/integrations/reads/counts.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +7 -1
- package/dist/plus/integrations/reads/drains.d.ts.map +1 -1
- package/dist/plus/integrations/reads/drains.js +13 -2
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
- package/dist/plus/integrations/reads/filters.js +22 -0
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/searchPullRequests.d.ts +4 -3
- package/dist/plus/integrations/reads/searchPullRequests.d.ts.map +1 -1
- package/dist/plus/integrations/reads/searchPullRequests.js +6 -4
- package/dist/plus/integrations/reads/searchPullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/sweeps.js +1 -1
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +2 -2
- package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
- package/dist/plus/integrations/reads/warnings.js +11 -2
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +6 -1
- package/dist/plus/integrations/results.d.ts.map +1 -1
- package/dist/plus/integrations/results.js.map +1 -1
- package/docs/integrations.md +76 -10
- package/docs/kepler-read-api-parity.md +9 -0
- package/package.json +1 -1
- package/src/git/models/pullRequest.ts +84 -0
- package/src/git/utils/pullRequest.utils.ts +50 -0
- package/src/plus/git-github/api/github.ts +176 -16
- package/src/plus/git-github/api/pullRequestSearchQuery.ts +81 -7
- package/src/plus/git-github/models.ts +29 -5
- package/src/plus/integrations/integrationService.ts +25 -2
- package/src/plus/integrations/manager.ts +50 -5
- package/src/plus/integrations/models/gitHostIntegration.ts +29 -0
- package/src/plus/integrations/models/integration.ts +1 -0
- package/src/plus/integrations/providerFilters.ts +6 -1
- package/src/plus/integrations/providers/accounts.ts +34 -0
- package/src/plus/integrations/providers/github.ts +133 -144
- package/src/plus/integrations/providers/gitlab.ts +28 -10
- package/src/plus/integrations/providers/models.ts +64 -124
- package/src/plus/integrations/providers/pullRequestReviews.ts +122 -0
- package/src/plus/integrations/reads/counts.ts +273 -2
- package/src/plus/integrations/reads/drains.ts +12 -2
- package/src/plus/integrations/reads/filters.ts +22 -0
- package/src/plus/integrations/reads/searchPullRequests.ts +12 -4
- package/src/plus/integrations/reads/sweeps.ts +1 -1
- package/src/plus/integrations/reads/warnings.ts +12 -1
- package/src/plus/integrations/results.ts +6 -1
|
@@ -1755,6 +1755,35 @@ export abstract class GitHostIntegration<
|
|
|
1755
1755
|
cancellation?: AbortSignal,
|
|
1756
1756
|
): Promise<(number | undefined)[] | undefined>;
|
|
1757
1757
|
|
|
1758
|
+
/** The PR twin of {@link countIssuesResult}: counts each scope's pull requests, transferring none. */
|
|
1759
|
+
async countPullRequestsResult(
|
|
1760
|
+
scopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: PullRequestSearchCriteria }[],
|
|
1761
|
+
cancellation?: AbortSignal,
|
|
1762
|
+
connectionId?: string,
|
|
1763
|
+
): Promise<IntegrationResult<(number | undefined)[] | undefined>> {
|
|
1764
|
+
const scope = getScopedLogger();
|
|
1765
|
+
// `connectionId` targets a specific account (multi-account); omitted reads the primary.
|
|
1766
|
+
const session = await this.resolveReadSession(connectionId, scope);
|
|
1767
|
+
if (session == null) return undefined;
|
|
1768
|
+
|
|
1769
|
+
const start = performance.now();
|
|
1770
|
+
try {
|
|
1771
|
+
const counts = await this.countProviderPullRequests?.(session, scopes, cancellation);
|
|
1772
|
+
this.resetRequestExceptionCount('countPullRequests');
|
|
1773
|
+
return { value: counts, duration: performance.now() - start };
|
|
1774
|
+
} catch (ex) {
|
|
1775
|
+
this.handleProviderException('countPullRequests', ex, { scope: scope, connectionId: connectionId });
|
|
1776
|
+
return { error: toError(ex), duration: performance.now() - start };
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
/** OPTIONAL, like {@link countProviderIssues}: only a provider that can count without fetching implements it. */
|
|
1781
|
+
protected countProviderPullRequests?(
|
|
1782
|
+
session: ProviderAuthenticationSession,
|
|
1783
|
+
scopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: PullRequestSearchCriteria }[],
|
|
1784
|
+
cancellation?: AbortSignal,
|
|
1785
|
+
): Promise<(number | undefined)[] | undefined>;
|
|
1786
|
+
|
|
1758
1787
|
getPullRequestIdentityFromMaybeUrl(search: string): PullRequestUrlIdentity | undefined {
|
|
1759
1788
|
return this.getProviderPullRequestIdentityFromMaybeUrl?.(search);
|
|
1760
1789
|
}
|
|
@@ -28,4 +28,9 @@ export type {
|
|
|
28
28
|
* same dependency-direction reason as the issue-search model above: provider API clients translate them below
|
|
29
29
|
* this package and cannot import this facade.
|
|
30
30
|
*/
|
|
31
|
-
export type {
|
|
31
|
+
export type {
|
|
32
|
+
PullRequestSearchCapabilities,
|
|
33
|
+
PullRequestSearchCriteria,
|
|
34
|
+
PullRequestSortField,
|
|
35
|
+
PullRequestSorting,
|
|
36
|
+
} from '../../git/models/pullRequest.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Account as ProviderAccount } from '@gitkraken/provider-apis';
|
|
2
|
+
import type { IssueMember } from '../../../git/models/issue.js';
|
|
3
|
+
import type { PullRequestMember } from '../../../git/models/pullRequest.js';
|
|
4
|
+
|
|
5
|
+
export function toProviderAccount(account: PullRequestMember | IssueMember): ProviderAccount {
|
|
6
|
+
return {
|
|
7
|
+
id: account.id ?? null,
|
|
8
|
+
avatarUrl: account.avatarUrl ?? null,
|
|
9
|
+
name: account.name ?? null,
|
|
10
|
+
url: account.url ?? null,
|
|
11
|
+
// TODO: Implement these in our own model
|
|
12
|
+
email: '',
|
|
13
|
+
username: account.name ?? null,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function fromProviderAccount(account: ProviderAccount | null): PullRequestMember | IssueMember {
|
|
18
|
+
return {
|
|
19
|
+
id: account?.id ?? '',
|
|
20
|
+
// An absent name stays absent. `'unknown'` was a display fallback invented in the provider layer, and a
|
|
21
|
+
// consumer couldn't tell it apart from a member genuinely named that — so it couldn't be undone where a
|
|
22
|
+
// name-shaped placeholder is wrong (rendering an avatar-only chip, or building an AI prompt, where
|
|
23
|
+
// `Assignees: unknown` reads as a real assignee). It also disagreed with {@link toIssueShape}, which
|
|
24
|
+
// collapsed the same absent name to `''` — same facade method, two fallbacks. Both now emit `undefined`.
|
|
25
|
+
name: account?.name ?? undefined,
|
|
26
|
+
avatarUrl: account?.avatarUrl ?? undefined,
|
|
27
|
+
// `url` is optional, so an absent one must be `undefined`, not `''`. `''` passes a `!= null` presence
|
|
28
|
+
// check and renders as a link to nowhere, and it disagreed with {@link toIssueShape} — which already
|
|
29
|
+
// collapses to `undefined` — even though BOTH mappers feed `listIssuesPage` (the repo-scoped path goes
|
|
30
|
+
// through `toIssueShape`, Azure's account-wide path through {@link fromProviderIssue}). Same facade
|
|
31
|
+
// method, two shapes. Matches {@link toProviderRepositoryShape}, which collapses every absent optional.
|
|
32
|
+
url: account?.url ?? undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { gitHubPullRequestRelationshipQualifiers } from '../../git-github/api/pullRequestSearchQuery.js';
|
|
1
2
|
import type { Account, UnidentifiedAuthor } from '../../../git/models/author.js';
|
|
2
3
|
import type { DefaultBranch } from '../../../git/models/defaultBranch.js';
|
|
3
4
|
import type { Issue, IssueSearchCriteria, IssueShape } from '../../../git/models/issue.js';
|
|
@@ -50,17 +51,6 @@ import {
|
|
|
50
51
|
} from './models.js';
|
|
51
52
|
import type { ProvidersApi } from './providersApi.js';
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
-
* Page size for GitHub's account-wide search reads: the maximum GitHub's search connection accepts, and what
|
|
55
|
-
* `GitHubApi.searchMyPullRequestsPage` already caps itself to.
|
|
56
|
-
*
|
|
57
|
-
* Requested explicitly rather than left to the SDK's own fallback. That fallback is also 100 as of
|
|
58
|
-
* `@gitkraken/provider-apis` 0.54.0 (it was 15 before, which cost a drained sweep ~7x the round trips it needs),
|
|
59
|
-
* so this is now a pin rather than an override — it keeps the size this read is tuned for from silently tracking
|
|
60
|
-
* a shared SDK default that other reads may want smaller.
|
|
61
|
-
*/
|
|
62
|
-
const githubSearchMaxPageSize = 100;
|
|
63
|
-
|
|
64
54
|
type GitHubPullRequestFacetCursor = Record<string, string>;
|
|
65
55
|
|
|
66
56
|
function toPullRequestFacetCursor(value: unknown): GitHubPullRequestFacetCursor {
|
|
@@ -91,13 +81,6 @@ function parsePullRequestFacetCursor(cursor: string | undefined): GitHubPullRequ
|
|
|
91
81
|
}
|
|
92
82
|
}
|
|
93
83
|
|
|
94
|
-
const pullRequestRelationshipQualifier: Record<PullRequestFilter, string> = {
|
|
95
|
-
[PullRequestFilter.Author]: 'author:@me',
|
|
96
|
-
[PullRequestFilter.Assignee]: 'assignee:@me',
|
|
97
|
-
[PullRequestFilter.ReviewRequested]: 'review-requested:@me',
|
|
98
|
-
[PullRequestFilter.Mention]: 'mentions:@me',
|
|
99
|
-
};
|
|
100
|
-
|
|
101
84
|
const metadata = providersMetadata[GitCloudHostIntegrationId.GitHub];
|
|
102
85
|
const authProvider: IntegrationAuthenticationProviderDescriptor = Object.freeze({
|
|
103
86
|
id: metadata.id,
|
|
@@ -441,140 +424,124 @@ abstract class GitHubIntegrationBase<ID extends GitHubIntegrationIds> extends Gi
|
|
|
441
424
|
},
|
|
442
425
|
): Promise<ProviderApiPagedResult<ProviderPullRequest> | undefined> {
|
|
443
426
|
const explicitFilters = options?.filters?.length ? [...new Set(options.filters)] : undefined;
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
427
|
+
// Every account-wide read goes through our own facet search — there is deliberately no SDK
|
|
428
|
+
// `getPullRequestsForUser` fallback. It could express neither a state set, nor exact relationships (its
|
|
429
|
+
// `involves:` is a different question), nor the full projection, so which reads it could serve depended on
|
|
430
|
+
// the options, and the two routes disagreed on more than they had to: only this one applies the Launchpad
|
|
431
|
+
// ignored/included repository and organization qualifiers, so the same read returned different pull
|
|
432
|
+
// requests depending on options that were supposed to affect only projection or relationship. With no
|
|
433
|
+
// state and no filters this emits a single facet with no relationship qualifier, which is GitHub's own
|
|
434
|
+
// `involves:@me` and `is:open` default — the same question the fallback asked, now on one route.
|
|
435
|
+
const github = await this.authenticationService.apis.github;
|
|
436
|
+
if (github == null) return undefined;
|
|
437
|
+
|
|
438
|
+
const requestedStates: PullRequestStateFilter[] =
|
|
439
|
+
options?.state != null && options.state.length > 0 ? [...new Set(options.state)] : ['open'];
|
|
440
|
+
const facets =
|
|
441
|
+
explicitFilters?.length != null
|
|
442
|
+
? requestedStates.flatMap(state =>
|
|
443
|
+
explicitFilters.map(filter => ({
|
|
444
|
+
key: `${state}:${filter}`,
|
|
445
|
+
state: state,
|
|
446
|
+
search: gitHubPullRequestRelationshipQualifiers[filter],
|
|
447
|
+
})),
|
|
448
|
+
)
|
|
449
|
+
: requestedStates.flatMap(state => [
|
|
450
|
+
{ key: state, state: state, search: undefined },
|
|
451
|
+
...(options?.includeReviewRequested === true
|
|
452
|
+
? [
|
|
453
|
+
{
|
|
454
|
+
key: `${state}:${PullRequestFilter.ReviewRequested}`,
|
|
455
|
+
state: state,
|
|
456
|
+
search: gitHubPullRequestRelationshipQualifiers[
|
|
457
|
+
PullRequestFilter.ReviewRequested
|
|
458
|
+
],
|
|
459
|
+
},
|
|
460
|
+
]
|
|
461
|
+
: []),
|
|
462
|
+
]);
|
|
463
|
+
const cursors = parsePullRequestFacetCursor(options?.cursor);
|
|
464
|
+
const hasResumableFacetCursor = Object.keys(cursors).length !== 0;
|
|
465
|
+
const facetsWithCursor = facets.filter(facet => cursors[facet.key] != null);
|
|
466
|
+
// The first call has no cursor, so query every requested state. A continuation only happens after a
|
|
467
|
+
// prior page reported `more:true`, whose bundle carries a cursor for each facet still in flight;
|
|
468
|
+
// facets absent from the bundle are exhausted, so re-querying them from scratch would refetch the
|
|
469
|
+
// same PRs (duplicated by the dedup-free sweep) and waste an API call per page. Query only the
|
|
470
|
+
// states that still have a cursor, but degrade a malformed/empty cursor bundle, or one that doesn't
|
|
471
|
+
// apply to the current requested states, to the first page rather than returning an empty page.
|
|
472
|
+
const facetsToQuery =
|
|
473
|
+
options?.cursor != null && hasResumableFacetCursor && facetsWithCursor.length !== 0
|
|
474
|
+
? facetsWithCursor
|
|
475
|
+
: facets;
|
|
476
|
+
const results = await Promise.allSettled(
|
|
477
|
+
facetsToQuery.map(async facet => ({
|
|
478
|
+
key: facet.key,
|
|
479
|
+
result: await github.searchMyPullRequestsPage(this, toTokenWithInfo(this.id, session), {
|
|
480
|
+
baseUrl: this.apiBaseUrl,
|
|
481
|
+
state: facet.state,
|
|
482
|
+
cursor: cursors[facet.key],
|
|
483
|
+
summary: options?.summary,
|
|
484
|
+
...(facet.search != null ? { search: facet.search, includeDefaultInvolvement: false } : undefined),
|
|
485
|
+
}),
|
|
486
|
+
})),
|
|
487
|
+
);
|
|
488
|
+
if (results.every(result => result.status === 'rejected')) {
|
|
489
|
+
const first = results[0];
|
|
490
|
+
if (first?.status === 'rejected') throw first.reason;
|
|
491
|
+
}
|
|
504
492
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
493
|
+
const values = new Map<string, ProviderPullRequest>();
|
|
494
|
+
const nextCursors: GitHubPullRequestFacetCursor = {};
|
|
495
|
+
const failures = [];
|
|
496
|
+
let hasMore = false;
|
|
497
|
+
let truncated = false;
|
|
498
|
+
let structuralIncompleteness = false;
|
|
499
|
+
let unkeyedPullRequest = 0;
|
|
500
|
+
for (const outcome of results) {
|
|
501
|
+
if (outcome.status === 'rejected') {
|
|
502
|
+
failures.push(toCollectionScopeFailure({ providerId: this.id }, outcome.reason));
|
|
503
|
+
truncated = true;
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
518
506
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
}
|
|
507
|
+
const { key, result } = outcome.value;
|
|
508
|
+
for (const pr of result.values) {
|
|
509
|
+
const mapped = toProviderPullRequest(pr);
|
|
510
|
+
const identity = getProviderPullRequestIdentity(mapped) ?? `unkeyed:${unkeyedPullRequest++}`;
|
|
511
|
+
if (!values.has(identity)) {
|
|
512
|
+
values.set(identity, mapped);
|
|
526
513
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
structuralIncompleteness = true;
|
|
531
|
-
} else {
|
|
532
|
-
hasMore = true;
|
|
533
|
-
nextCursors[key] = result.cursor;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
if (result.truncated) {
|
|
514
|
+
}
|
|
515
|
+
if (result.hasMore) {
|
|
516
|
+
if (result.cursor == null || result.cursor === '{}' || result.cursor === cursors[key]) {
|
|
537
517
|
truncated = true;
|
|
518
|
+
structuralIncompleteness = true;
|
|
519
|
+
} else {
|
|
520
|
+
hasMore = true;
|
|
521
|
+
nextCursors[key] = result.cursor;
|
|
538
522
|
}
|
|
539
523
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
paging: {
|
|
544
|
-
more: hasMore,
|
|
545
|
-
cursor: hasMore ? JSON.stringify({ type: 'cursor', cursors: nextCursors }) : '{}',
|
|
546
|
-
truncated: truncated || undefined,
|
|
547
|
-
},
|
|
548
|
-
...(failures.length || structuralIncompleteness
|
|
549
|
-
? {
|
|
550
|
-
metadata: {
|
|
551
|
-
completeness: 'partial' as const,
|
|
552
|
-
...(failures.length ? { failures: failures } : {}),
|
|
553
|
-
},
|
|
554
|
-
}
|
|
555
|
-
: undefined),
|
|
556
|
-
};
|
|
524
|
+
if (result.truncated) {
|
|
525
|
+
truncated = true;
|
|
526
|
+
}
|
|
557
527
|
}
|
|
558
528
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
pageSize: githubSearchMaxPageSize,
|
|
576
|
-
});
|
|
577
|
-
return result ?? undefined;
|
|
529
|
+
return {
|
|
530
|
+
values: [...values.values()],
|
|
531
|
+
paging: {
|
|
532
|
+
more: hasMore,
|
|
533
|
+
cursor: hasMore ? JSON.stringify({ type: 'cursor', cursors: nextCursors }) : '{}',
|
|
534
|
+
truncated: truncated || undefined,
|
|
535
|
+
},
|
|
536
|
+
...(failures.length || structuralIncompleteness
|
|
537
|
+
? {
|
|
538
|
+
metadata: {
|
|
539
|
+
completeness: 'partial' as const,
|
|
540
|
+
...(failures.length ? { failures: failures } : {}),
|
|
541
|
+
},
|
|
542
|
+
}
|
|
543
|
+
: undefined),
|
|
544
|
+
};
|
|
578
545
|
}
|
|
579
546
|
|
|
580
547
|
protected override async searchProviderMyIssues(
|
|
@@ -719,6 +686,28 @@ abstract class GitHubIntegrationBase<ID extends GitHubIntegrationIds> extends Gi
|
|
|
719
686
|
);
|
|
720
687
|
}
|
|
721
688
|
|
|
689
|
+
/**
|
|
690
|
+
* Counts several pull-request scopes in ONE request. Like {@link countProviderIssues}, GitHub's `search`
|
|
691
|
+
* reports `issueCount` on a zero-node selection, so a count preview costs no pull-request transfer.
|
|
692
|
+
*/
|
|
693
|
+
protected override async countProviderPullRequests(
|
|
694
|
+
session: ProviderAuthenticationSession,
|
|
695
|
+
scopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: PullRequestSearchCriteria }[],
|
|
696
|
+
cancellation?: AbortSignal,
|
|
697
|
+
): Promise<(number | undefined)[] | undefined> {
|
|
698
|
+
return (await this.authenticationService.apis.github)?.countPullRequests(
|
|
699
|
+
this,
|
|
700
|
+
toTokenWithInfo(this.id, session),
|
|
701
|
+
scopes.map(s => ({
|
|
702
|
+
repos: s.repos?.map(r => `${r.namespace}/${r.name}`),
|
|
703
|
+
org: s.org,
|
|
704
|
+
criteria: s.criteria,
|
|
705
|
+
})),
|
|
706
|
+
{ baseUrl: this.apiBaseUrl },
|
|
707
|
+
cancellation,
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
722
711
|
protected override async searchProviderPullRequests(
|
|
723
712
|
session: ProviderAuthenticationSession,
|
|
724
713
|
searchQuery: string,
|
|
@@ -23,6 +23,7 @@ import { toTokenWithInfo } from '../authentication/models.js';
|
|
|
23
23
|
import { toCollectionScopeFailure } from '../collectionMetadata.js';
|
|
24
24
|
import { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';
|
|
25
25
|
import type { IntegrationServiceContext } from '../context.js';
|
|
26
|
+
import { IntegrationReadUnavailableError } from '../errors.js';
|
|
26
27
|
import type { IntegrationConnectionChangeEvent } from '../integrationService.js';
|
|
27
28
|
import type { SearchMyPullRequestsOptions } from '../models/gitHostIntegration.js';
|
|
28
29
|
import { GitHostIntegration } from '../models/gitHostIntegration.js';
|
|
@@ -64,13 +65,19 @@ const cloudEnterpriseAuthProvider: IntegrationAuthenticationProviderDescriptor =
|
|
|
64
65
|
type GitLabPullRequestAssociation = 'assigned' | 'authored' | 'reviewRequested';
|
|
65
66
|
type GitLabPullRequestFacetCursor = Partial<Record<GitLabPullRequestAssociation, string>>;
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
/**
|
|
69
|
+
* The account-wide relationships GitLab can express, and the `scope` each maps to. `undefined` means GitLab has
|
|
70
|
+
* no equivalent axis (it has neither `Mention` nor `reviewed-by`), and this map is the single declaration of
|
|
71
|
+
* that. Exhaustive over `PullRequestFilter` on purpose: a new member must be decided for GitLab here rather
|
|
72
|
+
* than compiling through as silently unsupported. Exported for the advertised-vs-expressible capability test,
|
|
73
|
+
* which is what keeps it agreeing with `supportedAccountWidePullRequestFilters`; no runtime consumer imports it.
|
|
74
|
+
*/
|
|
75
|
+
export const gitLabAssociationForFilter: Record<PullRequestFilter, GitLabPullRequestAssociation | undefined> = {
|
|
71
76
|
[PullRequestFilter.Assignee]: 'assigned',
|
|
72
77
|
[PullRequestFilter.Author]: 'authored',
|
|
73
78
|
[PullRequestFilter.ReviewRequested]: 'reviewRequested',
|
|
79
|
+
[PullRequestFilter.Reviewed]: undefined,
|
|
80
|
+
[PullRequestFilter.Mention]: undefined,
|
|
74
81
|
};
|
|
75
82
|
|
|
76
83
|
function parseGitLabPullRequestFacetCursor(cursor: string | undefined): GitLabPullRequestFacetCursor {
|
|
@@ -473,15 +480,26 @@ abstract class GitLabIntegrationBase<ID extends GitLabIntegrationIds> extends Gi
|
|
|
473
480
|
|
|
474
481
|
const api = await this.getProvidersApi();
|
|
475
482
|
const requested = options?.filters?.length ? new Set(options.filters) : undefined;
|
|
483
|
+
// The filter contract is all-or-nothing, so refuse as soon as ONE requested member is inexpressible rather
|
|
484
|
+
// than only when every one is: dropping the rest would answer a narrower question than was asked
|
|
485
|
+
// (`[Author, Mention]` becoming `authored`). Throw rather than return `undefined`, which the facade reads
|
|
486
|
+
// as "no session could be resolved" and a drain turns into a not-connected warning, misattributing a
|
|
487
|
+
// refusal; `getMyPullRequestsForUserResult` recovers the throw into `{ error }`. Defense in depth:
|
|
488
|
+
// `resolveAccountWidePullRequestFilters` already refuses such a set, so only a direct caller of the public
|
|
489
|
+
// method reaches this.
|
|
490
|
+
const unsupported = requested == null ? [] : [...requested].filter(f => gitLabAssociationForFilter[f] == null);
|
|
491
|
+
if (unsupported.length > 0) {
|
|
492
|
+
throw new IntegrationReadUnavailableError(
|
|
493
|
+
this.name,
|
|
494
|
+
`account-wide pull request filters not expressible by GitLab: ${unsupported.join(', ')}`,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
476
498
|
const associations: GitLabPullRequestAssociation[] =
|
|
477
499
|
requested == null
|
|
478
500
|
? ['authored', 'assigned', 'reviewRequested']
|
|
479
|
-
: [
|
|
480
|
-
|
|
481
|
-
(filter): filter is Exclude<PullRequestFilter, PullRequestFilter.Mention> =>
|
|
482
|
-
filter !== PullRequestFilter.Mention,
|
|
483
|
-
)
|
|
484
|
-
.map(filter => gitLabAssociationForFilter[filter]);
|
|
501
|
+
: Array.from(requested, f => gitLabAssociationForFilter[f]).filter(a => a != null);
|
|
502
|
+
|
|
485
503
|
const cursors = parseGitLabPullRequestFacetCursor(options?.cursor);
|
|
486
504
|
const resumable = associations.filter(association => cursors[association] != null);
|
|
487
505
|
const associationsToQuery = options?.cursor != null && resumable.length > 0 ? resumable : associations;
|