@gitkraken/core-gitlens 0.5.114 → 0.5.116
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 +38 -1
- package/dist/plus/git-github/api/github.d.ts +3 -2
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +37 -37
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +2 -0
- package/dist/plus/integrations/collectionMetadata.d.ts.map +1 -1
- package/dist/plus/integrations/collectionMetadata.js +17 -1
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/context.d.ts +4 -1
- package/dist/plus/integrations/context.d.ts.map +1 -1
- package/dist/plus/integrations/index.d.ts +1 -0
- package/dist/plus/integrations/index.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +9 -1
- package/dist/plus/integrations/integrationService.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.js +8 -4
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +65 -6
- package/dist/plus/integrations/manager.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +22 -26
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +92 -47
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +9 -0
- package/dist/plus/integrations/models/integration.d.ts.map +1 -1
- package/dist/plus/integrations/models/integration.js +52 -19
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issueCache.d.ts +16 -0
- package/dist/plus/integrations/models/issueCache.d.ts.map +1 -0
- package/dist/plus/integrations/models/issueCache.js +9 -0
- package/dist/plus/integrations/models/issueCache.js.map +1 -0
- package/dist/plus/integrations/models/issueReads.d.ts +13 -0
- package/dist/plus/integrations/models/issueReads.d.ts.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts +11 -13
- package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.js +15 -1
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +3 -5
- package/dist/plus/integrations/providers/azureDevOps.d.ts.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.js +9 -8
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +3 -5
- package/dist/plus/integrations/providers/bitbucket-server.d.ts.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.js +2 -2
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts +3 -5
- package/dist/plus/integrations/providers/bitbucket.d.ts.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.js +2 -1
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +3 -5
- package/dist/plus/integrations/providers/github.d.ts.map +1 -1
- package/dist/plus/integrations/providers/github.js +3 -3
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +3 -5
- package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
- package/dist/plus/integrations/providers/gitlab.js +2 -2
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +3 -7
- package/dist/plus/integrations/providers/jira.d.ts.map +1 -1
- package/dist/plus/integrations/providers/jira.js +35 -22
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +7 -0
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts.map +1 -0
- package/dist/plus/integrations/providers/jiraIssueByKey.js +102 -0
- package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -0
- package/dist/plus/integrations/providers/linear.d.ts +3 -7
- package/dist/plus/integrations/providers/linear.d.ts.map +1 -1
- package/dist/plus/integrations/providers/linear.js +23 -22
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +3 -1
- package/dist/plus/integrations/providers/models.d.ts.map +1 -1
- package/dist/plus/integrations/providers/models.js +10 -4
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts +7 -0
- package/dist/plus/integrations/providers/providerErrors.d.ts.map +1 -0
- package/dist/plus/integrations/providers/providerErrors.js +91 -0
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -0
- package/dist/plus/integrations/providers/providersApi.d.ts +3 -2
- package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -1
- package/dist/plus/integrations/providers/providersApi.js +18 -52
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/trello.d.ts +2 -7
- package/dist/plus/integrations/providers/trello.d.ts.map +1 -1
- package/dist/plus/integrations/providers/trello.js +3 -1
- package/dist/plus/integrations/providers/trello.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts +2 -0
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +12 -7
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
- package/dist/plus/integrations/reads/broaden.js +26 -14
- package/dist/plus/integrations/reads/broaden.js.map +1 -1
- package/dist/plus/integrations/reads/counts.d.ts.map +1 -1
- package/dist/plus/integrations/reads/counts.js +39 -15
- package/dist/plus/integrations/reads/counts.js.map +1 -1
- package/dist/plus/integrations/reads/filters.d.ts +33 -11
- package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
- package/dist/plus/integrations/reads/filters.js +114 -25
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +23 -17
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/searchIssues.d.ts +1 -5
- package/dist/plus/integrations/reads/searchIssues.d.ts.map +1 -1
- package/dist/plus/integrations/reads/searchIssues.js +12 -6
- package/dist/plus/integrations/reads/searchIssues.js.map +1 -1
- package/dist/plus/integrations/reads/searchPullRequests.d.ts +1 -0
- package/dist/plus/integrations/reads/searchPullRequests.d.ts.map +1 -1
- package/dist/plus/integrations/reads/searchPullRequests.js +8 -3
- package/dist/plus/integrations/reads/searchPullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/trackerIssue.d.ts +21 -0
- package/dist/plus/integrations/reads/trackerIssue.d.ts.map +1 -0
- package/dist/plus/integrations/reads/trackerIssue.js +51 -0
- package/dist/plus/integrations/reads/trackerIssue.js.map +1 -0
- package/dist/plus/integrations/reads/warnings.d.ts +18 -15
- package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
- package/dist/plus/integrations/reads/warnings.js +37 -1
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +4 -20
- package/dist/plus/integrations/results.d.ts.map +1 -1
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/utils/promise.d.ts +2 -0
- package/dist/utils/promise.d.ts.map +1 -1
- package/dist/utils/promise.js +11 -0
- package/dist/utils/promise.js.map +1 -1
- package/docs/integrations.md +63 -28
- package/docs/kepler-read-api-parity.md +13 -0
- package/package.json +2 -2
- package/src/plus/git-github/api/github.ts +54 -60
- package/src/plus/integrations/collectionMetadata.ts +23 -1
- package/src/plus/integrations/context.ts +1 -1
- package/src/plus/integrations/index.ts +1 -0
- package/src/plus/integrations/integrationService.ts +14 -4
- package/src/plus/integrations/manager.ts +65 -6
- package/src/plus/integrations/models/gitHostIntegration.ts +117 -129
- package/src/plus/integrations/models/integration.ts +56 -22
- package/src/plus/integrations/models/issueCache.ts +29 -0
- package/src/plus/integrations/models/issueReads.ts +15 -0
- package/src/plus/integrations/models/issuesIntegration.ts +38 -8
- package/src/plus/integrations/providers/azureDevOps.ts +16 -13
- package/src/plus/integrations/providers/bitbucket-server.ts +4 -6
- package/src/plus/integrations/providers/bitbucket.ts +3 -4
- package/src/plus/integrations/providers/github.ts +5 -7
- package/src/plus/integrations/providers/gitlab.ts +4 -6
- package/src/plus/integrations/providers/jira.ts +52 -26
- package/src/plus/integrations/providers/jiraIssueByKey.ts +155 -0
- package/src/plus/integrations/providers/linear.ts +38 -25
- package/src/plus/integrations/providers/models.ts +17 -5
- package/src/plus/integrations/providers/providerErrors.ts +115 -0
- package/src/plus/integrations/providers/providersApi.ts +26 -68
- package/src/plus/integrations/providers/trello.ts +5 -4
- package/src/plus/integrations/providers/utils/providerPaging.ts +14 -7
- package/src/plus/integrations/reads/broaden.ts +35 -16
- package/src/plus/integrations/reads/counts.ts +62 -15
- package/src/plus/integrations/reads/filters.ts +131 -29
- package/src/plus/integrations/reads/issueTracker.ts +26 -24
- package/src/plus/integrations/reads/searchIssues.ts +23 -13
- package/src/plus/integrations/reads/searchPullRequests.ts +16 -2
- package/src/plus/integrations/reads/trackerIssue.ts +105 -0
- package/src/plus/integrations/reads/warnings.ts +42 -17
- package/src/plus/integrations/results.ts +4 -20
- package/src/utils/promise.ts +15 -0
|
@@ -20,15 +20,8 @@ import {
|
|
|
20
20
|
GitSelfManagedHostIntegrationId,
|
|
21
21
|
IssuesCloudHostIntegrationId,
|
|
22
22
|
} from '../constants.js';
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
AuthenticationErrorReason,
|
|
26
|
-
isRateLimitResponse,
|
|
27
|
-
RequestClientError,
|
|
28
|
-
RequestNotFoundError,
|
|
29
|
-
toError,
|
|
30
|
-
toRateLimitError,
|
|
31
|
-
} from '../errors.js';
|
|
23
|
+
import { RequestNotFoundError, toError } from '../errors.js';
|
|
24
|
+
import { requestJiraIssueByKey } from './jiraIssueByKey.js';
|
|
32
25
|
import type {
|
|
33
26
|
GetIssueFn,
|
|
34
27
|
GetIssuesForReposFn,
|
|
@@ -69,6 +62,7 @@ import type {
|
|
|
69
62
|
PullRequestFilter,
|
|
70
63
|
} from './models.js';
|
|
71
64
|
import { isRepoIdsInput, providersMetadata } from './models.js';
|
|
65
|
+
import { getProviderResponseBodyMessage, isProviderIssueNotFoundError, throwProviderError } from './providerErrors.js';
|
|
72
66
|
import {
|
|
73
67
|
collectProviderPagedResult,
|
|
74
68
|
mergeCollectionMetadata,
|
|
@@ -88,7 +82,6 @@ const createProviderApis: ProviderApisFactory =
|
|
|
88
82
|
// `Repository <x> not found`. Anchoring to that shape (rather than a loose `not found` substring) keeps
|
|
89
83
|
// the message fallbacks from misclassifying unrelated GraphQL/transport failures as a confident negative.
|
|
90
84
|
const repoNotFoundMessage = /^Repository .+ not found$/i;
|
|
91
|
-
|
|
92
85
|
// Duck-typed rather than `ex instanceof GraphQLErrors`, because importing the class as a value makes this
|
|
93
86
|
// module unloadable from plain Node ESM consumers: `@gitkraken/provider-apis` is CommonJS and declares its
|
|
94
87
|
// exports through getters, which Node's `cjs-module-lexer` cannot see, so the only named exports it can
|
|
@@ -562,43 +555,13 @@ export class ProvidersApi {
|
|
|
562
555
|
}
|
|
563
556
|
|
|
564
557
|
private handleProviderError<T>(tokenWithInfo: TokenWithInfo, error: any): T {
|
|
565
|
-
const { accessToken: token, ...tokenInfo } = tokenWithInfo;
|
|
566
558
|
const providerId = tokenWithInfo.providerId;
|
|
567
559
|
const provider = this.providers[providerId];
|
|
568
560
|
if (provider == null) {
|
|
569
561
|
throw new Error(`Provider with id ${providerId} not registered`);
|
|
570
562
|
}
|
|
571
563
|
|
|
572
|
-
|
|
573
|
-
const status: number = error.response.status;
|
|
574
|
-
switch (status) {
|
|
575
|
-
case 404: // Not found
|
|
576
|
-
case 410: // Gone
|
|
577
|
-
case 422: // Unprocessable Entity
|
|
578
|
-
throw new RequestNotFoundError(error);
|
|
579
|
-
case 429: // Too Many Requests
|
|
580
|
-
throw toRateLimitError(error, token);
|
|
581
|
-
case 401: // Unauthorized
|
|
582
|
-
case 403: // Forbidden
|
|
583
|
-
// A throttled request arrives on both statuses depending on the host (see
|
|
584
|
-
// `isRateLimitResponse`); 403 previously skipped the check, so every provider going through the
|
|
585
|
-
// SDK reported a rate limit as `auth` and asked the user to reconnect a healthy account.
|
|
586
|
-
if (isRateLimitResponse({ status: status, message: error.message })) {
|
|
587
|
-
throw toRateLimitError(error, token);
|
|
588
|
-
}
|
|
589
|
-
throw new AuthenticationError(
|
|
590
|
-
tokenInfo,
|
|
591
|
-
status === 401 ? AuthenticationErrorReason.Unauthorized : AuthenticationErrorReason.Forbidden,
|
|
592
|
-
error,
|
|
593
|
-
);
|
|
594
|
-
default:
|
|
595
|
-
if (status >= 400 && status < 500) {
|
|
596
|
-
throw new RequestClientError(error);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
throw error;
|
|
564
|
+
return throwProviderError(tokenWithInfo, error);
|
|
602
565
|
}
|
|
603
566
|
|
|
604
567
|
async getPagedResult<T>(
|
|
@@ -878,7 +841,7 @@ export class ProvidersApi {
|
|
|
878
841
|
/** See {@link GetIssuesOptions.sort}. Linear expresses `created`/`updated`, descending only. */
|
|
879
842
|
sort?: IssueSorting;
|
|
880
843
|
},
|
|
881
|
-
): Promise<
|
|
844
|
+
): Promise<ProviderApiPagedResult<ProviderIssue>> {
|
|
882
845
|
const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(
|
|
883
846
|
tokenOptInfo,
|
|
884
847
|
'getLinearIssuesFn',
|
|
@@ -1522,7 +1485,7 @@ export class ProvidersApi {
|
|
|
1522
1485
|
tokenOptInfo: TokenOptInfo,
|
|
1523
1486
|
reposOrIds: ProviderReposInput,
|
|
1524
1487
|
options?: GetIssuesOptions & { isPAT?: boolean; baseUrl?: string },
|
|
1525
|
-
): Promise<
|
|
1488
|
+
): Promise<ProviderApiPagedResult<ProviderIssue>> {
|
|
1526
1489
|
const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(
|
|
1527
1490
|
tokenOptInfo,
|
|
1528
1491
|
'getIssuesForReposFn',
|
|
@@ -1837,6 +1800,24 @@ export class ProvidersApi {
|
|
|
1837
1800
|
);
|
|
1838
1801
|
}
|
|
1839
1802
|
|
|
1803
|
+
async getJiraIssueByKey(
|
|
1804
|
+
tokenOptInfo: TokenWithInfo<IssuesCloudHostIntegrationId.Jira>,
|
|
1805
|
+
resourceId: string,
|
|
1806
|
+
resourceUrl: string,
|
|
1807
|
+
key: string,
|
|
1808
|
+
): Promise<ProviderIssue | undefined> {
|
|
1809
|
+
const { tokenWithInfo } = await this.ensureProviderToken(tokenOptInfo);
|
|
1810
|
+
|
|
1811
|
+
try {
|
|
1812
|
+
return await requestJiraIssueByKey(this.request, tokenWithInfo.accessToken, resourceId, resourceUrl, key);
|
|
1813
|
+
} catch (e) {
|
|
1814
|
+
const status = (e as { response?: { status?: unknown } }).response?.status;
|
|
1815
|
+
if (status === 404) return undefined;
|
|
1816
|
+
|
|
1817
|
+
return this.handleProviderError<ProviderIssue | undefined>(tokenWithInfo, e);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1840
1821
|
async getIssue(
|
|
1841
1822
|
tokenOptInfo: TokenWithInfo,
|
|
1842
1823
|
input: { resourceId: string; number: string } | { namespace: string; name: string; number: string },
|
|
@@ -1854,6 +1835,8 @@ export class ProvidersApi {
|
|
|
1854
1835
|
|
|
1855
1836
|
return result?.data;
|
|
1856
1837
|
} catch (e) {
|
|
1838
|
+
if (isProviderIssueNotFoundError(tokenWithInfo.providerId, e)) return undefined;
|
|
1839
|
+
|
|
1857
1840
|
return this.handleProviderError<ProviderIssue | undefined>(tokenWithInfo, e);
|
|
1858
1841
|
}
|
|
1859
1842
|
}
|
|
@@ -1896,28 +1879,3 @@ async function parseFetchResponseForApi<T>(response: Response): Promise<Provider
|
|
|
1896
1879
|
|
|
1897
1880
|
return result;
|
|
1898
1881
|
}
|
|
1899
|
-
|
|
1900
|
-
const maxProviderErrorBodyLength = 500;
|
|
1901
|
-
|
|
1902
|
-
/** Extracts the useful provider prose from an already-parsed SDK response body. */
|
|
1903
|
-
function getProviderResponseBodyMessage(body: unknown): string | undefined {
|
|
1904
|
-
let message: string | undefined;
|
|
1905
|
-
if (typeof body === 'string') {
|
|
1906
|
-
message = body;
|
|
1907
|
-
} else if (body != null && typeof body === 'object') {
|
|
1908
|
-
const { message: direct, error } = body as { message?: unknown; error?: unknown };
|
|
1909
|
-
if (typeof direct === 'string') {
|
|
1910
|
-
message = direct;
|
|
1911
|
-
} else if (typeof error === 'string') {
|
|
1912
|
-
message = error;
|
|
1913
|
-
} else if (error != null && typeof error === 'object') {
|
|
1914
|
-
const nested = (error as { message?: unknown }).message;
|
|
1915
|
-
if (typeof nested === 'string') {
|
|
1916
|
-
message = nested;
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
message = message?.trim();
|
|
1922
|
-
return message ? message.slice(0, maxProviderErrorBodyLength) : undefined;
|
|
1923
|
-
}
|
|
@@ -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 { Issue, IssueShape } from '../../../git/models/issue.js';
|
|
4
3
|
import type { IssueOrPullRequest, IssueOrPullRequestType } from '../../../git/models/issueOrPullRequest.js';
|
|
@@ -9,7 +8,7 @@ import type { ProviderAuthenticationSession } from '../authentication/models.js'
|
|
|
9
8
|
import { toTokenWithInfo } from '../authentication/models.js';
|
|
10
9
|
import { IssuesCloudHostIntegrationId } from '../constants.js';
|
|
11
10
|
import { IntegrationReadUnavailableError } from '../errors.js';
|
|
12
|
-
import type { IssuesForProjectOptions } from '../models/issueReads.js';
|
|
11
|
+
import type { IssuesForProjectOptions, ProjectIssuesDrain } from '../models/issueReads.js';
|
|
13
12
|
import { IssuesIntegration } from '../models/issuesIntegration.js';
|
|
14
13
|
import { fromProviderIssue, providersMetadata, toIssueShape } from './models.js';
|
|
15
14
|
|
|
@@ -104,7 +103,7 @@ export class TrelloIntegration extends IssuesIntegration<IssuesCloudHostIntegrat
|
|
|
104
103
|
session: ProviderAuthenticationSession,
|
|
105
104
|
project: ResourceDescriptor,
|
|
106
105
|
options?: IssuesForProjectOptions,
|
|
107
|
-
): Promise<
|
|
106
|
+
): Promise<ProjectIssuesDrain | undefined> {
|
|
108
107
|
// A non-issue descriptor genuinely has nothing to read (empty), but a missing app key is a broken read.
|
|
109
108
|
if (!isIssueResourceDescriptor(project)) return undefined;
|
|
110
109
|
|
|
@@ -141,7 +140,9 @@ export class TrelloIntegration extends IssuesIntegration<IssuesCloudHostIntegrat
|
|
|
141
140
|
// never a cursor. Surface that as terminal truncation; there is no next page to fetch, so retrying the
|
|
142
141
|
// same read cannot recover the omitted cards (D11).
|
|
143
142
|
const truncated = result.metadata != null && result.metadata.completeness !== 'complete';
|
|
144
|
-
return
|
|
143
|
+
return truncated
|
|
144
|
+
? { values: values, truncated: true, recovery: 'none', metadata: result.metadata }
|
|
145
|
+
: { values: values, truncated: false, metadata: result.metadata };
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
protected override searchProviderMyIssues(
|
|
@@ -32,17 +32,24 @@ export function parsePageCursor(cursor: string | undefined): number | undefined
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** Preserves successful sibling scopes, but doesn't turn an all-scope provider failure into an empty success. */
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
35
|
+
export function throwIfAllSettledFailed<T>(results: PromiseSettledResult<T>[]): void {
|
|
36
|
+
if (results.some(result => result.status === 'fulfilled')) return;
|
|
37
|
+
|
|
38
|
+
const rejected = results.find((result): result is PromiseRejectedResult => result.status === 'rejected');
|
|
39
|
+
if (rejected != null) throw rejected.reason;
|
|
40
|
+
}
|
|
42
41
|
|
|
42
|
+
export function flatSettledResultsOrThrow<T>(results: PromiseSettledResult<T[]>[]): T[] {
|
|
43
|
+
throwIfAllSettledFailed(results);
|
|
44
|
+
|
|
45
|
+
const fulfilled = results.filter((result): result is PromiseFulfilledResult<T[]> => result.status === 'fulfilled');
|
|
43
46
|
return fulfilled.flatMap(result => result.value);
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
export async function flatSettledOrThrow<T>(promises: Promise<T[]>[]): Promise<T[]> {
|
|
50
|
+
return flatSettledResultsOrThrow(await Promise.allSettled(promises));
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
/** Precedence for merged completeness: any known omission (`partial`) wins, then inability to confirm
|
|
47
54
|
* (`unknown`), and only an all-`complete` set of pages stays `complete`. */
|
|
48
55
|
const completenessRank: Record<CollectionCompleteness, number> = { partial: 2, unknown: 1, complete: 0 };
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
otherWarning,
|
|
26
26
|
truncationWarning,
|
|
27
27
|
unsupportedIssueSearchCriteriaWarning,
|
|
28
|
+
unusableSearchScopeMessage,
|
|
28
29
|
} from './warnings.js';
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -350,21 +351,6 @@ interface OrgIssuesRead {
|
|
|
350
351
|
nothingToRead?: boolean;
|
|
351
352
|
}
|
|
352
353
|
|
|
353
|
-
/**
|
|
354
|
-
* Whether this org can be read through the FILTERED issue search instead of the repository drain.
|
|
355
|
-
*
|
|
356
|
-
* Two conditions, and the second is not a formality: an empty org name is dropped by the provider's scope
|
|
357
|
-
* translation rather than rejected, which would leave a search of the WHOLE HOST — so the scope is validated
|
|
358
|
-
* through {@link resolveIssueSearchScope}, the same rule `searchIssuesPage` refuses on, rather than by an
|
|
359
|
-
* `org.name.length > 0` written here and free to drift from it.
|
|
360
|
-
*/
|
|
361
|
-
function canSearchOrgIssues(org: ProviderBroadenOrg): boolean {
|
|
362
|
-
return (
|
|
363
|
-
supportsFilteredIssueSearch(org.providerId) &&
|
|
364
|
-
resolveIssueSearchScope(undefined, org.name, undefined).rejection == null
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
354
|
/**
|
|
369
355
|
* The org-scoped filtered search: ONE request per page, whatever the org contains.
|
|
370
356
|
*
|
|
@@ -602,6 +588,34 @@ async function readOrgSlice(
|
|
|
602
588
|
);
|
|
603
589
|
}
|
|
604
590
|
|
|
591
|
+
// An org name NEITHER engine can use, refused before either is chosen. Declining the search for it is not
|
|
592
|
+
// enough, because falling through to the repository drain is not the safe default it looks like: the drain
|
|
593
|
+
// passes the SAME name to the org repository read, which matches nothing and reports `nothingToRead` — an
|
|
594
|
+
// org that reads as empty, with no warning and no `fetchFailed`, after up to 100 requests. Indistinguishable
|
|
595
|
+
// from an org that genuinely has no issues, and strictly worse than saying so.
|
|
596
|
+
//
|
|
597
|
+
// EVERY rejection, not just `unusable-scope`: with no repositories and no criteria the only other reachable
|
|
598
|
+
// one is `unscoped`, which here means exactly `org.name === ''` — the same bad outcome by the same route, so
|
|
599
|
+
// drawing the line between them would leave the emptiest case on the wrong side of the rule. (`repo-ids`
|
|
600
|
+
// cannot arise: no repositories are passed.) Refusing them all is also what lets the engine choice below be
|
|
601
|
+
// a plain capability question again, since no rejection survives this point.
|
|
602
|
+
const scopeRejection = resolveIssueSearchScope(undefined, org.name, undefined).rejection;
|
|
603
|
+
if (scopeRejection != null) {
|
|
604
|
+
return barrenSlice(
|
|
605
|
+
[
|
|
606
|
+
otherWarning(
|
|
607
|
+
org.providerId,
|
|
608
|
+
ctx.domainForRead(integration, org.providerId, connectionId, requestedDomain),
|
|
609
|
+
connectionId,
|
|
610
|
+
scopeRejection.reason === 'unusable-scope'
|
|
611
|
+
? unusableSearchScopeMessage('issue broadening scopes', scopeRejection.scopes)
|
|
612
|
+
: 'Issue broadening must name an organization; one was supplied with an empty name.',
|
|
613
|
+
),
|
|
614
|
+
],
|
|
615
|
+
{ fetchFailed: true },
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
|
|
605
619
|
// An org a prior round already drained must not be re-read: cursor-only providers would answer a
|
|
606
620
|
// fresh page-1 request with their first page again, duplicating issues across rounds. Skip it
|
|
607
621
|
// before any work (including the repo drain) and keep it marked exhausted so it stays skipped
|
|
@@ -614,7 +628,12 @@ async function readOrgSlice(
|
|
|
614
628
|
|
|
615
629
|
const domain = ctx.domainForRead(integration, org.providerId, connectionId, requestedDomain);
|
|
616
630
|
const cursor = getBroadenIssuesCursor(options.cursor, org, page, options.orgs.length);
|
|
617
|
-
|
|
631
|
+
// A plain capability question, and only because the refusal above has already turned away every org whose
|
|
632
|
+
// name the scope rule rejects. That order matters: `readOrgIssuesViaSearch` calls the integration's
|
|
633
|
+
// `searchIssuesPageResult` directly rather than the `searchIssuesPage` read, and that method documents its
|
|
634
|
+
// scope as already validated ("the facade rejects it before here") — so an unvalidated name reaching it would
|
|
635
|
+
// go straight to the provider query that sanitizes it.
|
|
636
|
+
const read = supportsFilteredIssueSearch(org.providerId)
|
|
618
637
|
? await readOrgIssuesViaSearch(integration, org, domain, cursor)
|
|
619
638
|
: await readOrgIssuesViaRepoDrain(integration, org, domain, cursor);
|
|
620
639
|
if (read.nothingToRead === true) {
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
otherWarning,
|
|
28
28
|
unsupportedIssueSearchCriteriaWarning,
|
|
29
29
|
unsupportedPullRequestSearchCriteriaWarning,
|
|
30
|
+
unusableSearchScopeMessage,
|
|
30
31
|
} from './warnings.js';
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -199,6 +200,23 @@ export async function countIssues(
|
|
|
199
200
|
return refused(issuesUnsupportedWarning(options.providerId, domain, options.connectionId));
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
// A provider with no filtered issue search has no count either: refuse ONCE for the provider rather than
|
|
204
|
+
// letting every scope repeat the same rejection. `undefined` criteria probes only the search's existence.
|
|
205
|
+
// Matches `countPullRequests`. Refusing it here rather than per scope also keeps the loop's warnings from
|
|
206
|
+
// being dominated by N copies of one provider-level fact — see the dedupe below, which handles the
|
|
207
|
+
// keyless refusals that remain.
|
|
208
|
+
const searchSupport = resolveIssueSearchCriteria(options.providerId, undefined);
|
|
209
|
+
if (searchSupport.rejection != null) {
|
|
210
|
+
return refused(
|
|
211
|
+
unsupportedIssueSearchCriteriaWarning(
|
|
212
|
+
options.providerId,
|
|
213
|
+
domain,
|
|
214
|
+
options.connectionId,
|
|
215
|
+
searchSupport.rejection,
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
202
220
|
const providerLimit = providersMetadata[options.providerId]?.issueSearchResultLimit;
|
|
203
221
|
const warnings: ProviderWarning[] = [];
|
|
204
222
|
let fetchFailed = false;
|
|
@@ -209,10 +227,12 @@ export async function countIssues(
|
|
|
209
227
|
for (const scope of options.scopes) {
|
|
210
228
|
const warning = rejectScope(options.providerId, domain, options.connectionId, scope);
|
|
211
229
|
if (warning != null) {
|
|
212
|
-
//
|
|
213
|
-
// keys were already refused above, so
|
|
214
|
-
//
|
|
215
|
-
warnings
|
|
230
|
+
// Deduped rather than pushed. The rejections that name the offending SCOPE embed its own key, and
|
|
231
|
+
// duplicate keys were already refused above, so those can never collapse. But the ones that report a
|
|
232
|
+
// PROVIDER-level fact — a criterion it cannot express, a contradictory relationship pair — name no
|
|
233
|
+
// scope, so several scopes failing the same way produce byte-identical warnings, and one refusal
|
|
234
|
+
// reported N times reads as N different problems.
|
|
235
|
+
appendDedupedWarning(warnings, warning);
|
|
216
236
|
fetchFailed = true;
|
|
217
237
|
continue;
|
|
218
238
|
}
|
|
@@ -304,8 +324,15 @@ function rejectScope(
|
|
|
304
324
|
connectionId: string | undefined,
|
|
305
325
|
scope: IssueCountScope,
|
|
306
326
|
): ProviderWarning | undefined {
|
|
327
|
+
const resolved = resolveIssueSearchCriteria(providerId, scope.criteria);
|
|
328
|
+
if (resolved.rejection != null) {
|
|
329
|
+
return unsupportedIssueSearchCriteriaWarning(providerId, domain, connectionId, resolved.rejection);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// AFTER the criteria check, mirroring `searchIssuesPage`: a provider with no filtered issue search is the more
|
|
333
|
+
// fundamental refusal, and a count must preview the refusal its read would give.
|
|
307
334
|
const scoping = resolveIssueSearchScope(scope.repos, scope.org, scope.criteria);
|
|
308
|
-
switch (scoping.rejection) {
|
|
335
|
+
switch (scoping.rejection?.reason) {
|
|
309
336
|
case 'repo-ids':
|
|
310
337
|
return otherWarning(
|
|
311
338
|
providerId,
|
|
@@ -320,11 +347,15 @@ function rejectScope(
|
|
|
320
347
|
connectionId,
|
|
321
348
|
`Issue count scope '${scope.key}' is unscoped; pass \`repos\`, \`org\`, or a relationship to the current user. \`any-assignee\` and \`unassigned\` are not scopes.`,
|
|
322
349
|
);
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
350
|
+
// A count applies exactly the qualifiers its search would, so an unusable scope would make it agree with
|
|
351
|
+
// the wrong search rather than expose it — see `isUsableSearchScopeName`.
|
|
352
|
+
case 'unusable-scope':
|
|
353
|
+
return otherWarning(
|
|
354
|
+
providerId,
|
|
355
|
+
domain,
|
|
356
|
+
connectionId,
|
|
357
|
+
unusableSearchScopeMessage('scopes', scoping.rejection.scopes, `Issue count scope '${scope.key}'`),
|
|
358
|
+
);
|
|
328
359
|
}
|
|
329
360
|
|
|
330
361
|
// Count-only, with no counterpart in the read: a relationship set is an OR across several searches, which one
|
|
@@ -435,9 +466,11 @@ export async function countPullRequests(
|
|
|
435
466
|
for (const scope of options.scopes) {
|
|
436
467
|
const warning = rejectPullRequestScope(options.providerId, domain, options.connectionId, scope);
|
|
437
468
|
if (warning != null) {
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
|
|
469
|
+
// Deduped rather than pushed: see the issue twin. Here the keyless set is wider — besides the criteria
|
|
470
|
+
// rejections, `unsupported-repository-scope` and `unsupported-organization-scope` are scope rejections
|
|
471
|
+
// that route through the criteria warning and name no key, and both are provider-level facts every
|
|
472
|
+
// scope would repeat.
|
|
473
|
+
appendDedupedWarning(warnings, warning);
|
|
441
474
|
fetchFailed = true;
|
|
442
475
|
continue;
|
|
443
476
|
}
|
|
@@ -516,7 +549,7 @@ function rejectPullRequestScope(
|
|
|
516
549
|
scope: PullRequestCountScope,
|
|
517
550
|
): ProviderWarning | undefined {
|
|
518
551
|
const scoping = resolvePullRequestSearchScope(providerId, scope.repos, scope.org, scope.criteria);
|
|
519
|
-
switch (scoping.rejection) {
|
|
552
|
+
switch (scoping.rejection?.reason) {
|
|
520
553
|
case 'repo-ids':
|
|
521
554
|
return otherWarning(
|
|
522
555
|
providerId,
|
|
@@ -536,9 +569,23 @@ function rejectPullRequestScope(
|
|
|
536
569
|
return unsupportedPullRequestSearchCriteriaWarning(providerId, domain, connectionId, {
|
|
537
570
|
reason: 'unsupported-criteria',
|
|
538
571
|
criteria: [
|
|
539
|
-
scoping.rejection === 'unsupported-repository-scope'
|
|
572
|
+
scoping.rejection.reason === 'unsupported-repository-scope'
|
|
573
|
+
? 'repositoryScope'
|
|
574
|
+
: 'organizationScope',
|
|
540
575
|
],
|
|
541
576
|
});
|
|
577
|
+
// See the issue twin, and `isUsableSearchScopeName` for the rule.
|
|
578
|
+
case 'unusable-scope':
|
|
579
|
+
return otherWarning(
|
|
580
|
+
providerId,
|
|
581
|
+
domain,
|
|
582
|
+
connectionId,
|
|
583
|
+
unusableSearchScopeMessage(
|
|
584
|
+
'scopes',
|
|
585
|
+
scoping.rejection.scopes,
|
|
586
|
+
`Pull request count scope '${scope.key}'`,
|
|
587
|
+
),
|
|
588
|
+
);
|
|
542
589
|
}
|
|
543
590
|
|
|
544
591
|
const resolved = resolvePullRequestSearchCriteria(providerId, scope.criteria);
|