@gitkraken/core-gitlens 0.5.103 → 0.5.105
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 +26 -1
- package/dist/git/models/issue.d.ts +78 -0
- package/dist/git/models/issue.d.ts.map +1 -1
- package/dist/git/models/issue.js.map +1 -1
- package/dist/plus/git-github/api/config.d.ts +12 -0
- package/dist/plus/git-github/api/config.d.ts.map +1 -1
- package/dist/plus/git-github/api/config.js +12 -1
- package/dist/plus/git-github/api/config.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +77 -7
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +241 -120
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/api/issueSearchQuery.d.ts +70 -0
- package/dist/plus/git-github/api/issueSearchQuery.d.ts.map +1 -0
- package/dist/plus/git-github/api/issueSearchQuery.js +143 -0
- package/dist/plus/git-github/api/issueSearchQuery.js.map +1 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts +1 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts.map +1 -1
- package/dist/plus/integrations/authentication/configuredIntegrationService.js +4 -1
- package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -0
- package/dist/plus/integrations/index.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +39 -2
- package/dist/plus/integrations/integrationService.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.js +46 -23
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +88 -2
- package/dist/plus/integrations/manager.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +61 -3
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +51 -0
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +3 -46
- 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/models/issueReads.d.ts +70 -0
- package/dist/plus/integrations/models/issueReads.d.ts.map +1 -0
- package/dist/plus/integrations/models/issueReads.js +2 -0
- package/dist/plus/integrations/models/issueReads.js.map +1 -0
- package/dist/plus/integrations/providerFilters.d.ts +10 -0
- package/dist/plus/integrations/providerFilters.d.ts.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +26 -3
- package/dist/plus/integrations/providers/github.d.ts.map +1 -1
- package/dist/plus/integrations/providers/github.js +34 -0
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +22 -0
- package/dist/plus/integrations/providers/models.d.ts.map +1 -1
- package/dist/plus/integrations/providers/models.js +26 -0
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.d.ts +10 -0
- package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
- package/dist/plus/integrations/reads/broaden.js.map +1 -1
- package/dist/plus/integrations/reads/counts.d.ts +64 -0
- package/dist/plus/integrations/reads/counts.d.ts.map +1 -0
- package/dist/plus/integrations/reads/counts.js +164 -0
- package/dist/plus/integrations/reads/counts.js.map +1 -0
- package/dist/plus/integrations/reads/filters.d.ts +63 -1
- package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
- package/dist/plus/integrations/reads/filters.js +125 -0
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issues.js +22 -41
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/paging.d.ts +59 -0
- package/dist/plus/integrations/reads/paging.d.ts.map +1 -1
- package/dist/plus/integrations/reads/paging.js +60 -0
- package/dist/plus/integrations/reads/paging.js.map +1 -1
- package/dist/plus/integrations/reads/searchIssues.d.ts +55 -0
- package/dist/plus/integrations/reads/searchIssues.d.ts.map +1 -0
- package/dist/plus/integrations/reads/searchIssues.js +144 -0
- package/dist/plus/integrations/reads/searchIssues.js.map +1 -0
- package/dist/plus/integrations/reads/warnings.d.ts +25 -1
- package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
- package/dist/plus/integrations/reads/warnings.js +86 -2
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/docs/integrations.md +110 -2
- package/docs/kepler-read-api-parity.md +57 -0
- package/package.json +1 -1
- package/src/git/models/issue.ts +81 -0
- package/src/plus/git-github/api/config.ts +13 -0
- package/src/plus/git-github/api/github.ts +332 -148
- package/src/plus/git-github/api/issueSearchQuery.ts +165 -0
- package/src/plus/integrations/authentication/configuredIntegrationService.ts +6 -1
- package/src/plus/integrations/index.ts +5 -0
- package/src/plus/integrations/integrationService.ts +130 -70
- package/src/plus/integrations/manager.ts +88 -2
- package/src/plus/integrations/models/gitHostIntegration.ts +102 -2
- package/src/plus/integrations/models/integration.ts +7 -46
- package/src/plus/integrations/models/issueReads.ts +73 -0
- package/src/plus/integrations/providerFilters.ts +15 -0
- package/src/plus/integrations/providers/github.ts +64 -2
- package/src/plus/integrations/providers/models.ts +49 -0
- package/src/plus/integrations/reads/broaden.ts +10 -0
- package/src/plus/integrations/reads/counts.ts +285 -0
- package/src/plus/integrations/reads/filters.ts +162 -1
- package/src/plus/integrations/reads/issues.ts +22 -45
- package/src/plus/integrations/reads/paging.ts +102 -0
- package/src/plus/integrations/reads/searchIssues.ts +252 -0
- package/src/plus/integrations/reads/warnings.ts +114 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CollectionMetadata, CollectionScopeFailure } from '@gitkraken/provider-apis';
|
|
2
2
|
import type { Account, UnidentifiedAuthor } from '../../../git/models/author.js';
|
|
3
3
|
import type { DefaultBranch } from '../../../git/models/defaultBranch.js';
|
|
4
|
-
import type { IssueShape, IssueStateFilter } from '../../../git/models/issue.js';
|
|
4
|
+
import type { IssueSearchCriteria, IssueShape, IssueStateFilter } from '../../../git/models/issue.js';
|
|
5
5
|
import type { IssueOrPullRequestState as PullRequestState } from '../../../git/models/issueOrPullRequest.js';
|
|
6
6
|
import type { PullRequest, PullRequestMergeMethod, PullRequestStateFilter } from '../../../git/models/pullRequest.js';
|
|
7
7
|
import type { RepositoryMetadata } from '../../../git/models/repositoryMetadata.js';
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
} from '../providers/models.js';
|
|
45
45
|
import type { ProvidersApi } from '../providers/providersApi.js';
|
|
46
46
|
import { mergeCollectionMetadata } from '../providers/utils/providerPaging.js';
|
|
47
|
-
import type { IntegrationResult, IntegrationType } from './integration.js';
|
|
47
|
+
import type { IntegrationResult, IntegrationType, ProviderIssueSearchPage } from './integration.js';
|
|
48
48
|
import { IntegrationBase } from './integration.js';
|
|
49
49
|
|
|
50
50
|
function isAzureDevOpsProvider(
|
|
@@ -1614,6 +1614,106 @@ export abstract class GitHostIntegration<
|
|
|
1614
1614
|
options?: { include?: PullRequestState[] },
|
|
1615
1615
|
): Promise<PullRequest[] | undefined>;
|
|
1616
1616
|
|
|
1617
|
+
/**
|
|
1618
|
+
* Result-returning wrapper for the FILTERED issue search — issues matching structured criteria over a
|
|
1619
|
+
* repository/org scope, with no forced relationship to the current user. Recovers thrown errors into
|
|
1620
|
+
* `{ error }` so the facade surfaces a warning instead of a silent empty page.
|
|
1621
|
+
*
|
|
1622
|
+
* Distinct from every other issue read on this class: {@link searchMyIssuesWithTruncationResult} is bound to
|
|
1623
|
+
* the current user by construction, and {@link getMyIssuesForReposAsShapesResult} goes through the SDK's
|
|
1624
|
+
* repo-scoped read (whose over-limit recovery walk can cost up to 128 requests). This one is one request per
|
|
1625
|
+
* page and carries no relationship it wasn't asked for.
|
|
1626
|
+
*/
|
|
1627
|
+
async searchIssuesPageResult(
|
|
1628
|
+
options: {
|
|
1629
|
+
/**
|
|
1630
|
+
* Repositories to search, as namespace/name descriptors. NOT repository ids: a search query names
|
|
1631
|
+
* repositories by path, so an id-based input can't be expressed and the facade rejects it before here.
|
|
1632
|
+
*/
|
|
1633
|
+
repos?: ProviderRepoInput[];
|
|
1634
|
+
org?: string;
|
|
1635
|
+
criteria?: IssueSearchCriteria;
|
|
1636
|
+
cursor?: string;
|
|
1637
|
+
pageSize?: number;
|
|
1638
|
+
},
|
|
1639
|
+
cancellation?: AbortSignal,
|
|
1640
|
+
connectionId?: string,
|
|
1641
|
+
): Promise<IntegrationResult<ProviderIssueSearchPage | undefined>> {
|
|
1642
|
+
const scope = getScopedLogger();
|
|
1643
|
+
// `connectionId` targets a specific account (multi-account); omitted reads the primary.
|
|
1644
|
+
const session = await this.resolveReadSession(connectionId, scope);
|
|
1645
|
+
if (session == null) return undefined;
|
|
1646
|
+
|
|
1647
|
+
const start = performance.now();
|
|
1648
|
+
try {
|
|
1649
|
+
const result = await this.searchProviderIssuesPage?.(session, options, cancellation);
|
|
1650
|
+
this.resetRequestExceptionCount('searchIssuesPage');
|
|
1651
|
+
return { value: result, duration: performance.now() - start };
|
|
1652
|
+
} catch (ex) {
|
|
1653
|
+
this.handleProviderException('searchIssuesPage', ex, { scope: scope, connectionId: connectionId });
|
|
1654
|
+
return { error: toError(ex), duration: performance.now() - start };
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* OPTIONAL, like {@link searchProviderPullRequests}: a provider that can't express the criteria server-side
|
|
1660
|
+
* doesn't implement it, and the facade refuses the read (warning + `fetchFailed`) rather than serving a list
|
|
1661
|
+
* that was never narrowed. Whether a provider implements this must agree with what
|
|
1662
|
+
* `ProviderMetadata.supportedIssueSearch` declares.
|
|
1663
|
+
*/
|
|
1664
|
+
protected searchProviderIssuesPage?(
|
|
1665
|
+
session: ProviderAuthenticationSession,
|
|
1666
|
+
options: {
|
|
1667
|
+
repos?: ProviderRepoInput[];
|
|
1668
|
+
org?: string;
|
|
1669
|
+
criteria?: IssueSearchCriteria;
|
|
1670
|
+
cursor?: string;
|
|
1671
|
+
pageSize?: number;
|
|
1672
|
+
},
|
|
1673
|
+
cancellation?: AbortSignal,
|
|
1674
|
+
): Promise<ProviderIssueSearchPage | undefined>;
|
|
1675
|
+
|
|
1676
|
+
/**
|
|
1677
|
+
* Result-returning wrapper for the count-only probe: how many issues MATCH each scope, transferring no issues
|
|
1678
|
+
* at all. Recovers thrown errors into `{ error }` like the reads around it.
|
|
1679
|
+
*
|
|
1680
|
+
* Counts come back POSITIONALLY — one per input scope, in order — because a caller's key must never reach the
|
|
1681
|
+
* provider query. `undefined` in a slot means the provider didn't report a count for it, never zero matches.
|
|
1682
|
+
*/
|
|
1683
|
+
async countIssuesResult(
|
|
1684
|
+
scopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: IssueSearchCriteria }[],
|
|
1685
|
+
cancellation?: AbortSignal,
|
|
1686
|
+
connectionId?: string,
|
|
1687
|
+
): Promise<IntegrationResult<(number | undefined)[] | undefined>> {
|
|
1688
|
+
const scope = getScopedLogger();
|
|
1689
|
+
// `connectionId` targets a specific account (multi-account); omitted reads the primary.
|
|
1690
|
+
const session = await this.resolveReadSession(connectionId, scope);
|
|
1691
|
+
if (session == null) return undefined;
|
|
1692
|
+
|
|
1693
|
+
const start = performance.now();
|
|
1694
|
+
try {
|
|
1695
|
+
const counts = await this.countProviderIssues?.(session, scopes, cancellation);
|
|
1696
|
+
this.resetRequestExceptionCount('countIssues');
|
|
1697
|
+
return { value: counts, duration: performance.now() - start };
|
|
1698
|
+
} catch (ex) {
|
|
1699
|
+
this.handleProviderException('countIssues', ex, { scope: scope, connectionId: connectionId });
|
|
1700
|
+
return { error: toError(ex), duration: performance.now() - start };
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* OPTIONAL: only a provider that can answer "how many match" WITHOUT fetching the matches implements this.
|
|
1706
|
+
* GitHub's search reports `issueCount` on a zero-node selection; GitLab's REST exposes a total on some
|
|
1707
|
+
* endpoints but not for a search-shaped query, and Azure has no equivalent. A provider that can't answer
|
|
1708
|
+
* doesn't implement it and the facade refuses the probe, so a consumer hides its count rather than being shown
|
|
1709
|
+
* a fabricated one.
|
|
1710
|
+
*/
|
|
1711
|
+
protected countProviderIssues?(
|
|
1712
|
+
session: ProviderAuthenticationSession,
|
|
1713
|
+
scopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: IssueSearchCriteria }[],
|
|
1714
|
+
cancellation?: AbortSignal,
|
|
1715
|
+
): Promise<(number | undefined)[] | undefined>;
|
|
1716
|
+
|
|
1617
1717
|
getPullRequestIdentityFromMaybeUrl(search: string): PullRequestUrlIdentity | undefined {
|
|
1618
1718
|
return this.getProviderPullRequestIdentityFromMaybeUrl?.(search);
|
|
1619
1719
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { CollectionMetadata } from '@gitkraken/provider-apis';
|
|
2
1
|
import type { Account } from '../../../git/models/author.js';
|
|
3
2
|
import type { AutolinkReference, DynamicAutolinkReference } from '../../../git/models/autolink.js';
|
|
4
3
|
import type { Issue, IssueShape } from '../../../git/models/issue.js';
|
|
@@ -26,12 +25,12 @@ import { GitCloudHostIntegrationId } from '../constants.js';
|
|
|
26
25
|
import type { IntegrationServiceContext } from '../context.js';
|
|
27
26
|
import { AuthenticationError, RequestClientError, toError } from '../errors.js';
|
|
28
27
|
import type { IntegrationConnectionChangeEvent } from '../integrationService.js';
|
|
29
|
-
import type { IssueFilter } from '../providerFilters.js';
|
|
30
28
|
import type { ProvidersApi } from '../providers/providersApi.js';
|
|
31
29
|
import type { Sources } from '../telemetry.js';
|
|
32
30
|
import { areDomainsOnSameHost } from '../utils/domain.utils.js';
|
|
33
31
|
import { isGitSelfManagedHostIntegrationId } from '../utils/integration.utils.js';
|
|
34
32
|
import type { GitHostIntegration } from './gitHostIntegration.js';
|
|
33
|
+
import type { AccountWideIssuesResult, SearchMyIssuesOptions } from './issueReads.js';
|
|
35
34
|
import type { IssuesIntegration } from './issuesIntegration.js';
|
|
36
35
|
|
|
37
36
|
export type Integration = GitHostIntegration | IssuesIntegration;
|
|
@@ -40,6 +39,10 @@ export type IntegrationById<T extends IntegrationIds> = T extends IssuesCloudHos
|
|
|
40
39
|
: GitHostIntegration;
|
|
41
40
|
export type IntegrationType = 'git' | 'issues';
|
|
42
41
|
|
|
42
|
+
// The issue-read contracts live in their own module (pure data, and their relationship to each other is the
|
|
43
|
+
// point of reading them together); re-exported here so the providers that implement these reads keep one import.
|
|
44
|
+
export type { AccountWideIssuesResult, ProviderIssueSearchPage, SearchMyIssuesOptions } from './issueReads.js';
|
|
45
|
+
|
|
43
46
|
export type IntegrationKey<T extends IntegrationIds = IntegrationIds> = T extends
|
|
44
47
|
| GitCloudHostIntegrationId
|
|
45
48
|
| IssuesCloudHostIntegrationId
|
|
@@ -53,50 +56,6 @@ export type IntegrationResult<T> =
|
|
|
53
56
|
| { error: Error; duration?: number; value?: never }
|
|
54
57
|
| undefined;
|
|
55
58
|
|
|
56
|
-
/**
|
|
57
|
-
* Account-wide issue read result. `truncated` is a provider-native incompleteness signal (GitHub's search
|
|
58
|
-
* ceiling, an Azure per-project backstop); cursor-capable providers also expose `cursor`/`hasMore`/`page`.
|
|
59
|
-
* `metadata` optionally carries structured per-scope failures from a fan-out (Azure across projects).
|
|
60
|
-
*/
|
|
61
|
-
export type AccountWideIssuesResult = {
|
|
62
|
-
values: IssueShape[];
|
|
63
|
-
truncated: boolean;
|
|
64
|
-
metadata?: CollectionMetadata;
|
|
65
|
-
cursor?: string;
|
|
66
|
-
hasMore?: boolean;
|
|
67
|
-
page?: number;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Options for the account-wide issue read. `includeAllAssignees` drops the "assigned to me" scoping so the
|
|
72
|
-
* read broadens to issues assigned to anyone (the account-wide equivalent of the repo-scoped
|
|
73
|
-
* {@link GitHostIntegration.getMyIssuesForReposResult}'s toggle). Authored/mentioned categories, where a
|
|
74
|
-
* provider has them, stay user-relative — they're meaningless without a user.
|
|
75
|
-
*/
|
|
76
|
-
export type SearchMyIssuesOptions = {
|
|
77
|
-
includeAllAssignees?: boolean;
|
|
78
|
-
/**
|
|
79
|
-
* Narrows the account-wide read to the requested relationship(s) instead of the provider's own definition of
|
|
80
|
-
* "my issues" (GitHub/GHE: authored ∪ assigned ∪ mentioned; Azure: assigned ∪ authored; GitLab:
|
|
81
|
-
* assigned-to-me). Narrowing has to happen HERE, not on the returned page: the excluded items still counted
|
|
82
|
-
* toward the provider's page/cursor, so a client-side filter desynchronizes `items` from `hasMore`.
|
|
83
|
-
*
|
|
84
|
-
* Only filters the provider can express server-side are accepted; the facade validates the set against
|
|
85
|
-
* `ProviderMetadata.supportedAccountWideIssueFilters` (all-or-nothing) and refuses the read rather than
|
|
86
|
-
* serving the unnarrowed union as if it had been filtered. Omitted keeps the provider's definition.
|
|
87
|
-
*/
|
|
88
|
-
filters?: IssueFilter[];
|
|
89
|
-
cursor?: string;
|
|
90
|
-
/**
|
|
91
|
-
* Narrows the account-wide read to one org/account (Azure: the organization) and/or one project within it.
|
|
92
|
-
* Only honored by a host with a project layer (Azure), whose account-wide read otherwise fans out over every
|
|
93
|
-
* project of every org; the caller checks `supportsProjectDiscovery` before asking, so a host without a
|
|
94
|
-
* project layer never silently returns an unscoped list as if it had been narrowed.
|
|
95
|
-
*/
|
|
96
|
-
org?: string;
|
|
97
|
-
project?: string;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
59
|
type SyncReqUsecase = Exclude<
|
|
101
60
|
| 'getAccountForCommit'
|
|
102
61
|
| 'getAccountForEmail'
|
|
@@ -120,7 +79,9 @@ type SyncReqUsecase = Exclude<
|
|
|
120
79
|
| 'getRepositoryMetadata'
|
|
121
80
|
| 'getResourcesForUser'
|
|
122
81
|
| 'getSshSigningKeysForEmails'
|
|
82
|
+
| 'countIssues'
|
|
123
83
|
| 'mergePullRequest'
|
|
84
|
+
| 'searchIssuesPage'
|
|
124
85
|
| 'searchMyIssues'
|
|
125
86
|
| 'searchMyPullRequests'
|
|
126
87
|
| 'searchPullRequests',
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { CollectionMetadata } from '@gitkraken/provider-apis';
|
|
2
|
+
import type { IssueShape } from '../../../git/models/issue.js';
|
|
3
|
+
import type { IssueFilter } from '../providerFilters.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The result and option contracts of the integration layer's issue reads — the shapes a PROVIDER returns to the
|
|
7
|
+
* facade, before the facade turns them into the paged/warning results a consumer sees.
|
|
8
|
+
*
|
|
9
|
+
* They live together, and apart from `IntegrationBase`, because they are pure data with one job: pinning what
|
|
10
|
+
* each issue read promises. Reading them side by side is also the point — `ProviderIssueSearchPage` is
|
|
11
|
+
* deliberately `AccountWideIssuesResult` plus a count, and that relationship is invisible when they sit at
|
|
12
|
+
* opposite ends of a 1000-line class module.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Account-wide issue read result. `truncated` is a provider-native incompleteness signal (GitHub's search
|
|
17
|
+
* ceiling, an Azure per-project backstop); cursor-capable providers also expose `cursor`/`hasMore`/`page`.
|
|
18
|
+
* `metadata` optionally carries structured per-scope failures from a fan-out (Azure across projects).
|
|
19
|
+
*/
|
|
20
|
+
export type AccountWideIssuesResult = {
|
|
21
|
+
values: IssueShape[];
|
|
22
|
+
truncated: boolean;
|
|
23
|
+
metadata?: CollectionMetadata;
|
|
24
|
+
cursor?: string;
|
|
25
|
+
hasMore?: boolean;
|
|
26
|
+
page?: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* One page of the FILTERED issue search (`GitHostIntegration.searchIssuesPageResult`).
|
|
31
|
+
*
|
|
32
|
+
* Carries what {@link AccountWideIssuesResult} does, plus `totalCount`: the number of matches the provider
|
|
33
|
+
* reported, which is what makes the result ceiling reportable as a figure ("N matched, at most M can be read")
|
|
34
|
+
* rather than as a bare `truncated` flag. `undefined` means the provider reported no count, never zero matches.
|
|
35
|
+
*/
|
|
36
|
+
export type ProviderIssueSearchPage = {
|
|
37
|
+
values: IssueShape[];
|
|
38
|
+
truncated: boolean;
|
|
39
|
+
cursor?: string;
|
|
40
|
+
hasMore: boolean;
|
|
41
|
+
page: number;
|
|
42
|
+
totalCount?: number;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Options for the account-wide issue read. `includeAllAssignees` drops the "assigned to me" scoping so the
|
|
47
|
+
* read broadens to issues assigned to anyone (the account-wide equivalent of the repo-scoped
|
|
48
|
+
* `GitHostIntegration.getMyIssuesForReposResult`'s toggle). Authored/mentioned categories, where a
|
|
49
|
+
* provider has them, stay user-relative — they're meaningless without a user.
|
|
50
|
+
*/
|
|
51
|
+
export type SearchMyIssuesOptions = {
|
|
52
|
+
includeAllAssignees?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Narrows the account-wide read to the requested relationship(s) instead of the provider's own definition of
|
|
55
|
+
* "my issues" (GitHub/GHE: authored ∪ assigned ∪ mentioned; Azure: assigned ∪ authored; GitLab:
|
|
56
|
+
* assigned-to-me). Narrowing has to happen HERE, not on the returned page: the excluded items still counted
|
|
57
|
+
* toward the provider's page/cursor, so a client-side filter desynchronizes `items` from `hasMore`.
|
|
58
|
+
*
|
|
59
|
+
* Only filters the provider can express server-side are accepted; the facade validates the set against
|
|
60
|
+
* `ProviderMetadata.supportedAccountWideIssueFilters` (all-or-nothing) and refuses the read rather than
|
|
61
|
+
* serving the unnarrowed union as if it had been filtered. Omitted keeps the provider's definition.
|
|
62
|
+
*/
|
|
63
|
+
filters?: IssueFilter[];
|
|
64
|
+
cursor?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Narrows the account-wide read to one org/account (Azure: the organization) and/or one project within it.
|
|
67
|
+
* Only honored by a host with a project layer (Azure), whose account-wide read otherwise fans out over every
|
|
68
|
+
* project of every org; the caller checks `supportsProjectDiscovery` before asking, so a host without a
|
|
69
|
+
* project layer never silently returns an unscoped list as if it had been narrowed.
|
|
70
|
+
*/
|
|
71
|
+
org?: string;
|
|
72
|
+
project?: string;
|
|
73
|
+
};
|
|
@@ -10,3 +10,18 @@ export enum IssueFilter {
|
|
|
10
10
|
Assignee = 'assignee',
|
|
11
11
|
Mention = 'mention',
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The filtered issue search's criteria model, re-exported from the git models so it can be reached from here
|
|
16
|
+
* alongside the two filter enums above.
|
|
17
|
+
*
|
|
18
|
+
* It lives in `@gitkraken/core-gitlens/git` rather than in this package because the provider implementations that translate it
|
|
19
|
+
* to a query — the GitHub API client among them — sit BELOW this package in the dependency graph and can't import
|
|
20
|
+
* from it. {@link IssueSearchRelationship} is a superset of {@link IssueFilter}: it adds the two user-independent
|
|
21
|
+
* relationships (`any-assignee`, `unassigned`) that the user-relative "my issues" filters have no way to name.
|
|
22
|
+
*/
|
|
23
|
+
export type {
|
|
24
|
+
IssueSearchCapabilities,
|
|
25
|
+
IssueSearchCriteria,
|
|
26
|
+
IssueSearchRelationship,
|
|
27
|
+
} from '../../git/models/issue.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Account, UnidentifiedAuthor } from '../../../git/models/author.js';
|
|
2
2
|
import type { DefaultBranch } from '../../../git/models/defaultBranch.js';
|
|
3
|
-
import type { Issue, IssueShape } from '../../../git/models/issue.js';
|
|
3
|
+
import type { Issue, IssueSearchCriteria, IssueShape } from '../../../git/models/issue.js';
|
|
4
4
|
import type { IssueOrPullRequest } from '../../../git/models/issueOrPullRequest.js';
|
|
5
5
|
import type {
|
|
6
6
|
PullRequest,
|
|
@@ -25,7 +25,7 @@ import { IntegrationReadUnavailableError } from '../errors.js';
|
|
|
25
25
|
import type { IntegrationConnectionChangeEvent } from '../integrationService.js';
|
|
26
26
|
import type { SearchMyPullRequestsOptions } from '../models/gitHostIntegration.js';
|
|
27
27
|
import { GitHostIntegration } from '../models/gitHostIntegration.js';
|
|
28
|
-
import type { SearchMyIssuesOptions } from '../models/integration.js';
|
|
28
|
+
import type { ProviderIssueSearchPage, SearchMyIssuesOptions } from '../models/integration.js';
|
|
29
29
|
import type { GitHubIntegrationIds } from './github/github.utils.js';
|
|
30
30
|
import { getGitHubPullRequestIdentityFromMaybeUrl } from './github/github.utils.js';
|
|
31
31
|
import type {
|
|
@@ -33,6 +33,7 @@ import type {
|
|
|
33
33
|
ProviderHierarchyResult,
|
|
34
34
|
ProviderOrganization,
|
|
35
35
|
ProviderPullRequest,
|
|
36
|
+
ProviderRepoInput,
|
|
36
37
|
ProviderRepository,
|
|
37
38
|
} from './models.js';
|
|
38
39
|
import {
|
|
@@ -592,6 +593,10 @@ abstract class GitHubIntegrationBase<ID extends GitHubIntegrationIds> extends Gi
|
|
|
592
593
|
cancellation?: AbortSignal,
|
|
593
594
|
options?: SearchMyIssuesOptions,
|
|
594
595
|
): Promise<{ values: IssueShape[]; truncated: boolean } | undefined> {
|
|
596
|
+
// `includeAllAssignees` becomes `assignee:*`, which needs a scope to mean anything — with none it matches
|
|
597
|
+
// millions of issues across all of GitHub rather than "assigned to anyone in the user's world". ANY scope
|
|
598
|
+
// works (one repo, several, or an org), so this refuses only the genuinely unscoped read; the scoped
|
|
599
|
+
// "assigned to anyone over these repos" request is served by `searchIssuesPage`.
|
|
595
600
|
if ((repos == null || repos.length === 0) && options?.includeAllAssignees) {
|
|
596
601
|
throw new IntegrationReadUnavailableError(
|
|
597
602
|
this.name,
|
|
@@ -620,6 +625,63 @@ abstract class GitHubIntegrationBase<ID extends GitHubIntegrationIds> extends Gi
|
|
|
620
625
|
);
|
|
621
626
|
}
|
|
622
627
|
|
|
628
|
+
/**
|
|
629
|
+
* The filtered issue search: one GraphQL request per page, no forced relationship to the current user, and no
|
|
630
|
+
* route through the SDK's repo-scoped read (whose over-limit recovery walk can spend up to 128 requests).
|
|
631
|
+
*
|
|
632
|
+
* The criteria → qualifier translation and the ordering guarantee live in the API client
|
|
633
|
+
* (`GitHubApi.searchIssuesPage`), which is also where user input is sanitized so it can't inject a qualifier.
|
|
634
|
+
*/
|
|
635
|
+
protected override async searchProviderIssuesPage(
|
|
636
|
+
session: ProviderAuthenticationSession,
|
|
637
|
+
options: {
|
|
638
|
+
repos?: ProviderRepoInput[];
|
|
639
|
+
org?: string;
|
|
640
|
+
criteria?: IssueSearchCriteria;
|
|
641
|
+
cursor?: string;
|
|
642
|
+
pageSize?: number;
|
|
643
|
+
},
|
|
644
|
+
cancellation?: AbortSignal,
|
|
645
|
+
): Promise<ProviderIssueSearchPage | undefined> {
|
|
646
|
+
return (await this.authenticationService.apis.github)?.searchIssuesPage(
|
|
647
|
+
this,
|
|
648
|
+
toTokenWithInfo(this.id, session),
|
|
649
|
+
{
|
|
650
|
+
// `namespace` is the owner for GitHub, which is how a `repo:` qualifier names a repository.
|
|
651
|
+
repos: options.repos?.map(r => `${r.namespace}/${r.name}`),
|
|
652
|
+
org: options.org,
|
|
653
|
+
criteria: options.criteria,
|
|
654
|
+
baseUrl: this.apiBaseUrl,
|
|
655
|
+
includeBody: true,
|
|
656
|
+
cursor: options.cursor,
|
|
657
|
+
pageSize: options.pageSize,
|
|
658
|
+
},
|
|
659
|
+
cancellation,
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Counts several scopes in ONE request. GitHub's `search` reports `issueCount` on a zero-node selection, so a
|
|
665
|
+
* count preview costs no issue transfer — measured, 30 aliased counts are a single rate-limit point.
|
|
666
|
+
*/
|
|
667
|
+
protected override async countProviderIssues(
|
|
668
|
+
session: ProviderAuthenticationSession,
|
|
669
|
+
scopes: readonly { repos?: ProviderRepoInput[]; org?: string; criteria?: IssueSearchCriteria }[],
|
|
670
|
+
cancellation?: AbortSignal,
|
|
671
|
+
): Promise<(number | undefined)[] | undefined> {
|
|
672
|
+
return (await this.authenticationService.apis.github)?.countIssues(
|
|
673
|
+
this,
|
|
674
|
+
toTokenWithInfo(this.id, session),
|
|
675
|
+
scopes.map(s => ({
|
|
676
|
+
repos: s.repos?.map(r => `${r.namespace}/${r.name}`),
|
|
677
|
+
org: s.org,
|
|
678
|
+
criteria: s.criteria,
|
|
679
|
+
})),
|
|
680
|
+
{ baseUrl: this.apiBaseUrl },
|
|
681
|
+
cancellation,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
|
|
623
685
|
protected override async searchProviderPullRequests(
|
|
624
686
|
session: ProviderAuthenticationSession,
|
|
625
687
|
searchQuery: string,
|
|
@@ -43,6 +43,7 @@ import type {
|
|
|
43
43
|
} from '@gitkraken/provider-apis';
|
|
44
44
|
import entityIdentifiersModule from '@gitkraken/provider-apis/entity-identifiers';
|
|
45
45
|
import providerUtilsModule from '@gitkraken/provider-apis/provider-utils';
|
|
46
|
+
import { githubSearchResultLimit } from '../../git-github/api/config.js';
|
|
46
47
|
import type { Account as UserAccount } from '../../../git/models/author.js';
|
|
47
48
|
import type { IssueMember, IssueProject, IssueShape, IssueStateFilter } from '../../../git/models/issue.js';
|
|
48
49
|
import { Issue, RepositoryAccessLevel } from '../../../git/models/issue.js';
|
|
@@ -74,9 +75,11 @@ import {
|
|
|
74
75
|
IssuesCloudHostIntegrationId,
|
|
75
76
|
} from '../constants.js';
|
|
76
77
|
import type { Integration, IntegrationType } from '../models/integration.js';
|
|
78
|
+
import type { IssueSearchCapabilities } from '../providerFilters.js';
|
|
77
79
|
import { IssueFilter, PullRequestFilter } from '../providerFilters.js';
|
|
78
80
|
|
|
79
81
|
export { IssueFilter, PullRequestFilter } from '../providerFilters.js';
|
|
82
|
+
export type { IssueSearchCapabilities, IssueSearchCriteria, IssueSearchRelationship } from '../providerFilters.js';
|
|
80
83
|
import type { ProviderRepositoryShape } from '../results.js';
|
|
81
84
|
|
|
82
85
|
export type { ProviderOrganization, ProviderRepositoryShape } from '../results.js';
|
|
@@ -710,11 +713,53 @@ export interface ProviderMetadata {
|
|
|
710
713
|
* axes; it omits `Mention` because that REST read has no first-class mention filter.
|
|
711
714
|
*/
|
|
712
715
|
supportedAccountWideIssueFilters?: IssueFilter[];
|
|
716
|
+
/**
|
|
717
|
+
* What the provider's FILTERED issue search (`searchIssuesPage`, and the `countIssues` probe over the same
|
|
718
|
+
* criteria) can express server-side. A third, wider surface than either filter set above: it is not bound to
|
|
719
|
+
* the user at all, so it takes relationships those reads have no way to name (`any-assignee`, `unassigned`),
|
|
720
|
+
* plus free text and issue attributes.
|
|
721
|
+
*
|
|
722
|
+
* Absent means the provider has NO filtered issue search and the read is refused. Present, it is a promise:
|
|
723
|
+
* every field declared here reaches the provider query, so a consumer that intersects against it never has a
|
|
724
|
+
* read refused, and a criterion is never silently ignored.
|
|
725
|
+
*/
|
|
726
|
+
supportedIssueSearch?: IssueSearchCapabilities;
|
|
727
|
+
/**
|
|
728
|
+
* The maximum number of results the provider's filtered issue search will serve for ONE query, however it is
|
|
729
|
+
* paged. Matches past it are UNREACHABLE, not merely unfetched, which is why a read that hits this reports an
|
|
730
|
+
* omission with `recovery: 'none'` and the total match count rather than offering a "load more".
|
|
731
|
+
*
|
|
732
|
+
* Absent means the provider declares no ceiling, in which case a truncated read falls back to generic wording
|
|
733
|
+
* instead of quoting a limit that was never published.
|
|
734
|
+
*/
|
|
735
|
+
issueSearchResultLimit?: number;
|
|
713
736
|
}
|
|
714
737
|
|
|
715
738
|
export type Providers = Record<IntegrationIds, ProviderInfo>;
|
|
716
739
|
export type ProvidersMetadata = Record<IntegrationIds, ProviderMetadata>;
|
|
717
740
|
|
|
741
|
+
/**
|
|
742
|
+
* GitHub and GitHub Enterprise express every criterion as a search qualifier on the same GraphQL `search` field,
|
|
743
|
+
* so the two share one declaration — a GHE instance running the same search syntax has the same capability, and
|
|
744
|
+
* two copies of this table would be free to drift.
|
|
745
|
+
*
|
|
746
|
+
* Every entry is a claim the implementation must keep: `searchIssuesPage` emits a qualifier for each of these,
|
|
747
|
+
* and a test asserts that (see the `issueSearch` capability tests) so the table can't quietly outrun the code.
|
|
748
|
+
*/
|
|
749
|
+
const githubIssueSearchCapabilities: IssueSearchCapabilities = {
|
|
750
|
+
// `author:@me` / `assignee:@me` / `mentions:@me` / `assignee:*` / `no:assignee`, each its own aliased search.
|
|
751
|
+
relationships: ['authored', 'assigned', 'mentioned', 'any-assignee', 'unassigned'],
|
|
752
|
+
text: true,
|
|
753
|
+
labels: true,
|
|
754
|
+
milestone: true,
|
|
755
|
+
updatedAfter: true,
|
|
756
|
+
createdAfter: true,
|
|
757
|
+
// `-linked:pr`.
|
|
758
|
+
withoutLinkedPullRequest: true,
|
|
759
|
+
// `is:open` / `is:closed`, or neither for all states.
|
|
760
|
+
states: true,
|
|
761
|
+
};
|
|
762
|
+
|
|
718
763
|
export const providersMetadata: ProvidersMetadata = {
|
|
719
764
|
[GitCloudHostIntegrationId.GitHub]: {
|
|
720
765
|
domain: 'github.com',
|
|
@@ -742,6 +787,8 @@ export const providersMetadata: ProvidersMetadata = {
|
|
|
742
787
|
// The account-wide read is three independent searches (`author:@me`, `assignee:@me`, `mentions:@me`) behind
|
|
743
788
|
// one composite cursor, so any subset of them is expressible.
|
|
744
789
|
supportedAccountWideIssueFilters: [IssueFilter.Author, IssueFilter.Assignee, IssueFilter.Mention],
|
|
790
|
+
supportedIssueSearch: githubIssueSearchCapabilities,
|
|
791
|
+
issueSearchResultLimit: githubSearchResultLimit,
|
|
745
792
|
scopes: ['repo', 'read:user', 'user:email'],
|
|
746
793
|
},
|
|
747
794
|
[GitSelfManagedHostIntegrationId.CloudGitHubEnterprise]: {
|
|
@@ -770,6 +817,8 @@ export const providersMetadata: ProvidersMetadata = {
|
|
|
770
817
|
// The account-wide read is three independent searches (`author:@me`, `assignee:@me`, `mentions:@me`) behind
|
|
771
818
|
// one composite cursor, so any subset of them is expressible.
|
|
772
819
|
supportedAccountWideIssueFilters: [IssueFilter.Author, IssueFilter.Assignee, IssueFilter.Mention],
|
|
820
|
+
supportedIssueSearch: githubIssueSearchCapabilities,
|
|
821
|
+
issueSearchResultLimit: githubSearchResultLimit,
|
|
773
822
|
scopes: ['repo', 'read:user', 'user:email'],
|
|
774
823
|
},
|
|
775
824
|
[GitCloudHostIntegrationId.GitLab]: {
|
|
@@ -23,6 +23,16 @@ import { gitHostOnlySurfaceWarning, issuesUnsupportedWarning, noConnectionWarnin
|
|
|
23
23
|
* provider positions — one per org — so its continuation is a per-org cursor BUNDLE (see `cursors.ts`) and its
|
|
24
24
|
* failure attribution is per provider across those orgs. Both are the reason a page number alone can't address a
|
|
25
25
|
* later page, and why {@link broadenIssues} walks prior pages itself when given only `page`.
|
|
26
|
+
*
|
|
27
|
+
* SUPERSEDED for a caller that already knows its repositories: `searchIssuesPage({ repos, criteria })` answers
|
|
28
|
+
* the same question in one request per page, with no repository drain and no route through the SDK read whose
|
|
29
|
+
* over-limit recovery walk can spend up to 128 requests. This read stays for "fan out across these orgs,
|
|
30
|
+
* whatever repos they contain", whose per-provider attribution the single-provider search doesn't produce.
|
|
31
|
+
*
|
|
32
|
+
* If you migrate a caller, note the semantics carefully: broadening means ALL VISIBLE — it passes
|
|
33
|
+
* `includeAllAssignees: true`, which resolves to no assignee constraint at all, so unassigned issues ARE
|
|
34
|
+
* included. The equivalent is an OMITTED `relationships`, not `['any-assignee']`: `assignee:*` means "has some
|
|
35
|
+
* assignee" and would silently exclude every unassigned issue, which is the opposite of broadening.
|
|
26
36
|
*/
|
|
27
37
|
|
|
28
38
|
export interface BroadenIssuesOptions {
|