@gitkraken/core-gitlens 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -1
- package/README.md +23 -15
- package/dist/git/errors.d.ts +6 -5
- package/dist/git/errors.js +7 -6
- package/dist/git/errors.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +1 -1
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/remoteProvider.d.ts +4 -1
- package/dist/git/models/remoteProvider.js.map +1 -1
- package/dist/git/providers/provider.d.ts +4 -2
- package/dist/git/utils/branch.utils.d.ts +4 -1
- package/dist/git/utils/branch.utils.js +5 -2
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/utils/mergeConflicts.utils.js +2 -2
- package/dist/git/utils/mergeConflicts.utils.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +14 -3
- package/dist/git-cli/cliGitProvider.js +71 -34
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +7 -1
- package/dist/git-cli/exec/git.js +19 -4
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/providers/branchRemote.js +2 -5
- package/dist/git-cli/providers/branchRemote.js.map +1 -1
- package/dist/git-cli/providers/branches.js +4 -4
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.js +6 -3
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.js +2 -2
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/staging.js +1 -1
- package/dist/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/errors.d.ts +4 -1
- package/dist/plus/ai/errors.js +4 -0
- package/dist/plus/ai/errors.js.map +1 -1
- package/dist/plus/ai/providers/mistralProvider.js +1 -1
- package/dist/plus/ai/providers/mistralProvider.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +2 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
- package/dist/plus/git-github/api/github.js +12 -13
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/api/token.d.ts +2 -0
- package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts +1 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.js +6 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -1
- package/dist/plus/integrations/authentication/configuredIntegrationService.js +2 -2
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +18 -5
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts +3 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js +4 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -1
- package/dist/plus/integrations/authentication/models.d.ts +2 -0
- package/dist/plus/integrations/authentication/models.js +2 -1
- package/dist/plus/integrations/authentication/models.js.map +1 -1
- package/dist/plus/integrations/authentication/observedAuthenticationProvider.js +1 -1
- package/dist/plus/integrations/authentication/rejectedTokenTracker.d.ts +5 -4
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js +5 -4
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js +1 -1
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +6 -0
- package/dist/plus/integrations/integrationService.js +3 -3
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/lite.js +1 -1
- package/dist/plus/integrations/manager.d.ts +51 -3
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +14 -0
- package/dist/plus/integrations/models/gitHostIntegration.js +50 -41
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +27 -5
- package/dist/plus/integrations/models/integration.js +57 -15
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +6 -0
- package/dist/plus/integrations/models/issuesIntegration.js +11 -11
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +2 -2
- package/dist/plus/integrations/providers/azureDevOps.js +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.js +1 -1
- package/dist/plus/integrations/providers/bitbucket.js +1 -1
- package/dist/plus/integrations/providers/github.js +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.js +2 -2
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.js +1 -1
- package/dist/plus/integrations/providers/jira-server.js +2 -2
- package/dist/plus/integrations/providers/jira.js +1 -1
- package/dist/plus/integrations/providers/linear.d.ts +4 -4
- package/dist/plus/integrations/providers/linear.js +26 -9
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/models.js +2 -1
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.js +9 -0
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +2 -0
- package/dist/plus/integrations/providers/providersApi.js +1 -1
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/trello.js +1 -1
- package/dist/plus/integrations/providers/utils.js +1 -1
- package/dist/plus/integrations/reads/counts.js +1 -1
- package/dist/plus/integrations/reads/cursors.js +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +5 -4
- package/dist/plus/integrations/reads/drains.js +28 -8
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +1 -1
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts +4 -1
- package/dist/plus/integrations/reads/issueTracker.js +1 -1
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -0
- package/dist/plus/integrations/reads/issues.js +5 -3
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/paging.d.ts +5 -6
- package/dist/plus/integrations/reads/paging.js +5 -6
- package/dist/plus/integrations/reads/paging.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts +4 -0
- package/dist/plus/integrations/reads/pullRequests.js +4 -2
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +1 -1
- package/dist/plus/integrations/reads/sweeps.js +42 -10
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +23 -3
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/plus/notifications/api.d.ts +20 -0
- package/dist/plus/notifications/api.js +89 -0
- package/dist/plus/notifications/api.js.map +1 -0
- package/dist/plus/notifications/context.d.ts +28 -0
- package/dist/plus/notifications/context.js +0 -0
- package/dist/plus/notifications/errors.d.ts +13 -0
- package/dist/plus/notifications/errors.js +22 -0
- package/dist/plus/notifications/errors.js.map +1 -0
- package/dist/plus/notifications/models.d.ts +94 -0
- package/dist/plus/notifications/models.js +0 -0
- package/dist/plus/notifications/notificationsService.d.ts +115 -0
- package/dist/plus/notifications/notificationsService.js +381 -0
- package/dist/plus/notifications/notificationsService.js.map +1 -0
- package/dist/plus/notifications/parse.d.ts +12 -0
- package/dist/plus/notifications/parse.js +152 -0
- package/dist/plus/notifications/parse.js.map +1 -0
- package/dist/plus/notifications/presentation.d.ts +28 -0
- package/dist/plus/notifications/presentation.js +55 -0
- package/dist/plus/notifications/presentation.js.map +1 -0
- package/dist/utils/env/node/exec.d.ts +10 -1
- package/dist/utils/env/node/exec.js +41 -4
- package/dist/utils/env/node/exec.js.map +1 -1
- package/docs/integrations.md +25 -3
- package/docs/notifications.md +239 -0
- package/package.json +22 -2
|
@@ -2,8 +2,8 @@ import { PromiseCache } from "../../../utils/promiseCache.js";
|
|
|
2
2
|
import { isIssueResourceDescriptor } from "../../../git/utils/resourceDescriptor.utils.js";
|
|
3
3
|
import { IntegrationReadUnavailableError } from "../errors.js";
|
|
4
4
|
import "../constants.js";
|
|
5
|
-
import { toTokenWithInfo } from "../authentication/models.js";
|
|
6
5
|
import { fromProviderIssue, providersMetadata, toIssueShape } from "./models.js";
|
|
6
|
+
import { toTokenWithInfo } from "../authentication/models.js";
|
|
7
7
|
import { IssuesIntegration } from "../models/issuesIntegration.js";
|
|
8
8
|
//#region ../plus/integrations/src/providers/trello.ts
|
|
9
9
|
const metadata = providersMetadata["trello"];
|
|
@@ -2,8 +2,8 @@ import { Logger as _logger } from "../../../utils/logger.js";
|
|
|
2
2
|
import { isIssueResourceDescriptor, isRepositoryDescriptor } from "../../../git/utils/resourceDescriptor.utils.js";
|
|
3
3
|
import "../constants.js";
|
|
4
4
|
import { getSingleConfiguredDomain } from "../utils/domain.utils.js";
|
|
5
|
-
import { isCloudGitSelfManagedHostIntegrationId, isGitSelfManagedHostIntegrationId, isSelfManagedHostIntegrationId } from "../utils/integration.utils.js";
|
|
6
5
|
import { isGitHubDotCom, isGitLabDotCom } from "./models.js";
|
|
6
|
+
import { isCloudGitSelfManagedHostIntegrationId, isGitSelfManagedHostIntegrationId, isSelfManagedHostIntegrationId } from "../utils/integration.utils.js";
|
|
7
7
|
//#region ../plus/integrations/src/providers/utils.ts
|
|
8
8
|
const EntityIdentifierProviderType = {
|
|
9
9
|
Azure: "azure",
|
|
@@ -2,8 +2,8 @@ import { chunk } from "../../../utils/array.js";
|
|
|
2
2
|
import { mapBounded } from "../../../utils/promise.js";
|
|
3
3
|
import "../constants.js";
|
|
4
4
|
import { appendDedupedWarning, toProviderWarning } from "../results.js";
|
|
5
|
-
import { isGitHostIntegration, isIssuesHostIntegrationId, warnOnMissingSessionForDomain } from "../utils/integration.utils.js";
|
|
6
5
|
import { providersMetadata } from "../providers/models.js";
|
|
6
|
+
import { isGitHostIntegration, isIssuesHostIntegrationId, warnOnMissingSessionForDomain } from "../utils/integration.utils.js";
|
|
7
7
|
import { gitHostOnlySurfaceWarning, issuesUnsupportedWarning, otherWarning, unsupportedIssueSearchCriteriaWarning, unsupportedPullRequestSearchCriteriaWarning, unsupportedWarning, unusableSearchScopeMessage } from "./warnings.js";
|
|
8
8
|
import { runCaptured } from "./drains.js";
|
|
9
9
|
import { resolveIssueSearchCriteria, resolveIssueSearchScope, resolvePullRequestSearchCriteria, resolvePullRequestSearchScope } from "./filters.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { hostFromDomain } from "../utils/domain.utils.js";
|
|
2
1
|
import { toPageCursor } from "../providers/utils/providerPaging.js";
|
|
2
|
+
import { hostFromDomain } from "../utils/domain.utils.js";
|
|
3
3
|
import { pageToCursor } from "./paging.js";
|
|
4
4
|
//#region ../plus/integrations/src/reads/cursors.ts
|
|
5
5
|
/** Positive safe integers only, deduped; `undefined` when nothing survives (so the field is omitted). */
|
|
@@ -52,11 +52,12 @@ export declare function discoverResources(integration: IssuesIntegration, id: In
|
|
|
52
52
|
*/
|
|
53
53
|
export declare function drainPullRequests(integration: GitHostIntegration, id: IntegrationIds, domain: string | undefined, repos: ProviderReposInput, state: PullRequestStateFilter[] | undefined, filters: PullRequestFilter[] | undefined,
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* The sweep options the drain reads beyond its query: the breadth opt-ins only the account-wide read can
|
|
56
|
+
* express, and the repo-scoped read's fan-out bound and clone-URL lookup. Grouped rather than passed
|
|
57
|
+
* positionally because the opt-ins are same-typed neighbors a transposition would silently swap, and picked
|
|
58
|
+
* from the sweep options rather than restated so a new one is declared once.
|
|
58
59
|
*/
|
|
59
|
-
|
|
60
|
+
sweepOptions: Pick<PullRequestSweepOptions, 'includeReviewRequested' | 'includeReviews' | 'scopeConcurrency' | 'includeRemoteInfo'> | undefined, connectionId: string | undefined, maxPages: number, attributeUnavailableProvider: boolean): Promise<{
|
|
60
61
|
items: (ProviderPullRequest | PullRequest)[];
|
|
61
62
|
warnings: ProviderWarning[];
|
|
62
63
|
fetchFailed: boolean;
|
|
@@ -2,8 +2,9 @@ import { defaultPullRequestSort } from "../../../git/models/pullRequest.js";
|
|
|
2
2
|
import { supportedOrderedCloudIntegrationIds } from "../constants.js";
|
|
3
3
|
import { appendDedupedWarning, reconcileOmissionsWithFailure, toProviderWarning } from "../results.js";
|
|
4
4
|
import { mergeAssessmentInto } from "../collectionMetadata.js";
|
|
5
|
-
import {
|
|
5
|
+
import { hostFromDomain } from "../utils/domain.utils.js";
|
|
6
6
|
import { getProviderPullRequestIdentity, pullRequestNamesPeople } from "../providers/models.js";
|
|
7
|
+
import { isIssuesHostIntegrationId, isSelfManagedHostIntegrationId } from "../utils/integration.utils.js";
|
|
7
8
|
import { cappedBesideBudgetWarning, noConnectionWarning, pullRequestSearchCapResultWarning, truncationWarning } from "./warnings.js";
|
|
8
9
|
//#region ../plus/integrations/src/reads/drains.ts
|
|
9
10
|
/**
|
|
@@ -59,7 +60,7 @@ async function discoverResources(integration, id, domain, connectionId, options)
|
|
|
59
60
|
* `maxPages` (marking `truncated`) or on a hard read failure (marking `fetchFailed`), keeping the
|
|
60
61
|
* pages fetched so far. A soft warning (`{ value, error }`) is recorded but the drain continues.
|
|
61
62
|
*/
|
|
62
|
-
async function drainPullRequests(integration, id, domain, repos, state, filters,
|
|
63
|
+
async function drainPullRequests(integration, id, domain, repos, state, filters, sweepOptions, connectionId, maxPages, attributeUnavailableProvider) {
|
|
63
64
|
const items = [];
|
|
64
65
|
const itemIndexByIdentity = /* @__PURE__ */ new Map();
|
|
65
66
|
const warnings = [];
|
|
@@ -76,8 +77,8 @@ async function drainPullRequests(integration, id, domain, repos, state, filters,
|
|
|
76
77
|
if (unexplainedTruncation && (cap == null || cause === "interrupted")) appendDedupedWarning(warnings, truncationWarning(id, domain, connectionId, "Pull request", cause));
|
|
77
78
|
};
|
|
78
79
|
const accountWide = repos.length === 0;
|
|
79
|
-
const includeReviewRequested = accountWide && (
|
|
80
|
-
const includeReviews = accountWide && (
|
|
80
|
+
const includeReviewRequested = accountWide && (sweepOptions?.includeReviewRequested ?? false);
|
|
81
|
+
const includeReviews = accountWide && (sweepOptions?.includeReviews ?? false);
|
|
81
82
|
/** Every cursor already followed, so a provider that cycles them can't keep the drain walking in circles. */
|
|
82
83
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
83
84
|
for (;;) {
|
|
@@ -92,7 +93,9 @@ async function drainPullRequests(integration, id, domain, repos, state, filters,
|
|
|
92
93
|
}, connectionId) : integration.getMyPullRequestsForReposResult(repos, {
|
|
93
94
|
state,
|
|
94
95
|
filters,
|
|
95
|
-
cursor: pageCursor
|
|
96
|
+
cursor: pageCursor,
|
|
97
|
+
scopeConcurrency: sweepOptions?.scopeConcurrency,
|
|
98
|
+
includeRemoteInfo: sweepOptions?.includeRemoteInfo
|
|
96
99
|
}, connectionId));
|
|
97
100
|
if (warning != null) appendDedupedWarning(warnings, warning);
|
|
98
101
|
if (value == null) {
|
|
@@ -228,14 +231,31 @@ async function drainRepositories(integration, id, domain, org, project, connecti
|
|
|
228
231
|
cursor = nextCursor;
|
|
229
232
|
}
|
|
230
233
|
}
|
|
234
|
+
function describeSweepTargetSelector(target) {
|
|
235
|
+
const parts = [];
|
|
236
|
+
if (target.connectionId != null) parts.push(`connectionId '${target.connectionId}'`);
|
|
237
|
+
if (target.domain != null) parts.push(`domain '${target.domain}'`);
|
|
238
|
+
return parts.length !== 0 ? ` (${parts.join(", ")})` : "";
|
|
239
|
+
}
|
|
231
240
|
function resolvePullRequestSweepTargets(options) {
|
|
232
241
|
if (options?.targets != null) {
|
|
233
242
|
if (options.providerIds != null || options.connectionId != null) throw new TypeError("Pull request sweep 'targets' cannot be combined with 'providerIds' or 'connectionId'");
|
|
234
|
-
const
|
|
243
|
+
const seenTargets = /* @__PURE__ */ new Set();
|
|
244
|
+
const targetCounts = /* @__PURE__ */ new Map();
|
|
245
|
+
const unnamedTargets = [];
|
|
235
246
|
for (const target of options.targets) {
|
|
236
|
-
|
|
237
|
-
|
|
247
|
+
const host = isSelfManagedHostIntegrationId(target.providerId) ? hostFromDomain(target.domain) : void 0;
|
|
248
|
+
const key = JSON.stringify([
|
|
249
|
+
target.providerId,
|
|
250
|
+
target.connectionId ?? "",
|
|
251
|
+
host ?? ""
|
|
252
|
+
]);
|
|
253
|
+
if (seenTargets.has(key)) throw new TypeError(`Pull request sweep targets must be unique; duplicate '${target.providerId}' target${describeSweepTargetSelector(target)}`);
|
|
254
|
+
seenTargets.add(key);
|
|
255
|
+
targetCounts.set(target.providerId, (targetCounts.get(target.providerId) ?? 0) + 1);
|
|
256
|
+
if (target.connectionId == null && host == null) unnamedTargets.push(target);
|
|
238
257
|
}
|
|
258
|
+
for (const target of unnamedTargets) if ((targetCounts.get(target.providerId) ?? 0) > 1) throw new TypeError(`Pull request sweep targets for '${target.providerId}' must each name a connectionId or domain when there is more than one (a domain only on a self-managed host); an unnamed target reads the primary connection`);
|
|
239
259
|
return {
|
|
240
260
|
targets: options.targets,
|
|
241
261
|
attributeUnavailableProviders: true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drains.js","names":[],"sources":["../../../../../plus/integrations/src/reads/drains.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { PullRequest, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { defaultPullRequestSort } from '@gitlens/git/models/pullRequest.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { supportedOrderedCloudIntegrationIds } from '../constants.js';\nimport type { ProviderSweepTarget, PullRequestSweepOptions } from '../manager.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { PullRequestFilter } from '../providerFilters.js';\nimport type { ProviderPullRequest, ProviderReposInput, ProviderRepository } from '../providers/models.js';\nimport { getProviderPullRequestIdentity, pullRequestNamesPeople } from '../providers/models.js';\nimport type { ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure, toProviderWarning } from '../results.js';\nimport { isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport {\n\tcappedBesideBudgetWarning,\n\tnoConnectionWarning,\n\tpullRequestSearchCapResultWarning,\n\ttruncationWarning,\n} from './warnings.js';\n\n/**\n * The all-pages reads: a provider read run repeatedly until it runs out of pages, as opposed to the single-page\n * reads the facade's paged surface exposes. `sweepPullRequests` and `broadenIssues` are built on these.\n *\n * A drain's job is to keep going where a single page would stop, so what distinguishes it is its failure\n * bookkeeping rather than its loop: it must return the prefix it did read plus enough signal for the caller to\n * know the result is not authoritative — `truncated` when pages remain unread (a page backstop, or a provider\n * claiming another page while handing back no usable cursor), `fetchFailed` when a page or scope failed\n * outright. Discarding the prefix on a late failure, or returning it as if complete, are the two mistakes these\n * exist to prevent.\n */\n\n/**\n * Runs a result-returning read and captures failure as a neutral {@link ProviderWarning} rather than\n * letting it throw or silently vanish. Handles both a returned `{ error }` (the read cores' contract)\n * and a hard throw; a soft warning (`{ value, error }`) yields the value *and* a warning.\n */\nexport async function runCaptured<T>(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfn: () => Promise<IntegrationResult<T>>,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: T; warning?: ProviderWarning }> {\n\ttry {\n\t\tconst result = await fn();\n\t\tif (result == null) {\n\t\t\t// The read core returns undefined only when it couldn't resolve a session. For a per-connection\n\t\t\t// or explicit-domain read that means the requested target is gone or its authentication is invalid,\n\t\t\t// which must not be reported as an empty account. The untargeted primary path legitimately yields\n\t\t\t// nothing when the provider isn't connected, so leave it as an empty result.\n\t\t\treturn connectionId != null || options?.warnOnMissingSession\n\t\t\t\t? { warning: noConnectionWarning(id, domain, connectionId) }\n\t\t\t\t: {};\n\t\t}\n\t\tif (result.error != null) {\n\t\t\treturn { value: result.value, warning: toProviderWarning(id, domain, connectionId, result.error) };\n\t\t}\n\t\treturn { value: result.value };\n\t} catch (ex) {\n\t\treturn { warning: toProviderWarning(id, domain, connectionId, ex) };\n\t}\n}\n\n/**\n * {@link runCaptured} over an issue tracker's resource (site, organization, board) discovery. A read that resolved a\n * session yet answered with no data and no error carries `completeness: 'unknown'` metadata, so it reads as an\n * incomplete discovery rather than as an account with no resources, which `runCaptured` alone can't tell apart from\n * an untargeted read with no session.\n */\nexport async function discoverResources(\n\tintegration: IssuesIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: ResourceDescriptor[]; warning?: ProviderWarning; metadata?: CollectionMetadata }> {\n\tlet noData = false;\n\tconst captured = await runCaptured(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tasync () => {\n\t\t\tconst result = await integration.getResourcesForUserResult(connectionId);\n\t\t\tnoData = result != null && result.error == null && result.value == null;\n\t\t\treturn result;\n\t\t},\n\t\toptions,\n\t);\n\treturn noData ? { ...captured, metadata: { completeness: 'unknown' } } : captured;\n}\n\n/**\n * Drains every page of the user's pull requests for one git-host integration, threading the opaque\n * next-cursor the provider returns (so it works for both page- and cursor-based hosts). Stops at\n * `maxPages` (marking `truncated`) or on a hard read failure (marking `fetchFailed`), keeping the\n * pages fetched so far. A soft warning (`{ value, error }`) is recorded but the drain continues.\n */\nexport async function drainPullRequests(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\trepos: ProviderReposInput,\n\tstate: PullRequestStateFilter[] | undefined,\n\tfilters: PullRequestFilter[] | undefined,\n\t/**\n\t * Breadth opt-ins that only the account-wide read can express. Grouped rather than passed positionally\n\t * because they are same-typed neighbors a transposition would silently swap, and picked from the sweep\n\t * options rather than restated so a third opt-in is declared once.\n\t */\n\taccountWideOptions: Pick<PullRequestSweepOptions, 'includeReviewRequested' | 'includeReviews'> | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n\tattributeUnavailableProvider: boolean,\n): Promise<{\n\titems: (ProviderPullRequest | PullRequest)[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tfailedProvider: boolean;\n}> {\n\tconst items: (ProviderPullRequest | PullRequest)[] = [];\n\tconst itemIndexByIdentity = new Map<string, number>();\n\tconst warnings: ProviderWarning[] = [];\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\t// SDK metadata failures across pages mean the collection is incomplete even when no page threw; carry\n\t// this through the terminal returns instead of resetting it to false at the last page.\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\t// A page the provider capped or couldn't vouch for, and which no other warning already explains — decides\n\t// whether this drain raises one of its own for that part.\n\tlet unexplainedTruncation = false;\n\t// The largest match count reported on a truncated page, from which the drain derives the provider's cap.\n\tlet truncatedTotalCount: number | undefined;\n\tconst capWarning = (): ProviderWarning | undefined =>\n\t\tpullRequestSearchCapResultWarning(id, domain, connectionId, truncatedTotalCount, defaultPullRequestSort);\n\tconst appendTruncationWarnings = (cause: 'interrupted' | 'exhausted'): void => {\n\t\tconst cap = capWarning();\n\t\tif (cap != null) {\n\t\t\tappendDedupedWarning(warnings, cap);\n\t\t}\n\t\t// A cap explains the missing tail of a read that succeeded; a failed read still owes its own warning.\n\t\tif (unexplainedTruncation && (cap == null || cause === 'interrupted')) {\n\t\t\tappendDedupedWarning(warnings, truncationWarning(id, domain, connectionId, 'Pull request', cause));\n\t\t}\n\t};\n\n\t// With no repos this is an account-wide \"my PRs\" sweep. The repo-scoped core rejects an empty `repos`\n\t// input, so read the provider-native account-wide core instead.\n\tconst accountWide = repos.length === 0;\n\t// The breadth opt-ins are only expressible on the account-wide core, so gate them here — the single place\n\t// that already knows which core it is about to call.\n\tconst includeReviewRequested = accountWide && (accountWideOptions?.includeReviewRequested ?? false);\n\tconst includeReviews = accountWide && (accountWideOptions?.includeReviews ?? false);\n\t/** Every cursor already followed, so a provider that cycles them can't keep the drain walking in circles. */\n\tconst seenCursors = new Set<string>();\n\n\tfor (;;) {\n\t\tpage++;\n\t\t// Snapshot the mutable loop cursor so the read closure doesn't capture a later-reassigned value.\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(id, domain, connectionId, () =>\n\t\t\taccountWide\n\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\t\t\tincludeReviewRequested: includeReviewRequested,\n\t\t\t\t\t\t\tfilters: filters,\n\t\t\t\t\t\t\t// Lite by default; the full projection (reviews) only when a caller opts in.\n\t\t\t\t\t\t\tsummary: !includeReviews,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t)\n\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\trepos,\n\t\t\t\t\t\t{ state: state, filters: filters, cursor: pageCursor },\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t),\n\t\t);\n\t\tif (warning != null) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\t// An implicit sweep may silently skip a provider that has no session before it yields data.\n\t\t\t// Once a page has been returned, however, losing that session leaves an unread tail and must\n\t\t\t// be attributed even when the provider wasn't explicitly requested.\n\t\t\tconst sessionLostAfterProgress = warning == null && page > 1;\n\t\t\tconst unavailable = warning == null && (attributeUnavailableProvider || sessionLostAfterProgress);\n\t\t\tif (unavailable) {\n\t\t\t\tappendDedupedWarning(warnings, noConnectionWarning(id, domain, connectionId));\n\t\t\t}\n\t\t\t// `warning` set → a hard read failure (incomplete items); otherwise not connected / no session.\n\t\t\tconst failed = fetchFailed || warning != null || unavailable;\n\t\t\t// A cap seen on an earlier page still left results out, so it is still worth saying — but as part\n\t\t\t// of a read that failed, never as an omission. (`failed` is always true here when anything was\n\t\t\t// latched: reaching this exit past page 1 means a later page was lost.)\n\t\t\tappendTruncationWarnings(failed ? 'interrupted' : 'exhausted');\n\t\t\t// An earlier page may already have emitted an omission before this one died; it asserts the read\n\t\t\t// succeeded, which is no longer true.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || sessionLostAfterProgress,\n\t\t\t\t// Only a top-level first-page rejection means the provider itself failed. A later-page or\n\t\t\t\t// per-scope failure still yielded a usable provider slice and stays represented separately.\n\t\t\t\tfailedProvider: page === 1 && (warning != null || unavailable),\n\t\t\t};\n\t\t}\n\n\t\t// Composite account-wide queries can surface the same PR on different relationship/state pages\n\t\t// (for example authored + review-requested, or closed + merged). Keep the first stable position but\n\t\t// replace its value with the latest representation so a later, richer merged state wins. Repository\n\t\t// identity is what keys a row (see `getProviderPullRequestIdentity`), falling back to the canonical\n\t\t// URL; a row with neither stays unkeyed and is retained so unrelated incomplete rows are never\n\t\t// collapsed.\n\t\tfor (const pullRequest of value.values) {\n\t\t\tconst identity = getProviderPullRequestIdentity(pullRequest);\n\t\t\tif (identity == null) {\n\t\t\t\titems.push(pullRequest);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existingIndex = itemIndexByIdentity.get(identity);\n\t\t\tif (existingIndex == null) {\n\t\t\t\titemIndexByIdentity.set(identity, items.length);\n\t\t\t\titems.push(pullRequest);\n\t\t\t} else {\n\t\t\t\titems[existingIndex] = pullRequest;\n\t\t\t}\n\t\t}\n\n\t\t// Assess this page's SDK metadata: append scope-aware warnings (deduped across pages), and remember\n\t\t// whether a structured failure or incompleteness occurred.\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\tconst pageTruncated =\n\t\t\t(value as { truncated?: boolean }).truncated === true ||\n\t\t\tvalue.paging?.truncated === true ||\n\t\t\tassessment.truncated;\n\t\ttruncated = truncated || pageTruncated;\n\t\t// Whether this drain owes its OWN warning for a capped page — `mergeAssessmentInto` has already appended\n\t\t// one when the fact came from SDK metadata, and repeating it would be noise.\n\t\tunexplainedTruncation = unexplainedTruncation || (pageTruncated && !assessment.truncated);\n\t\t// Only the account-wide read reports a search match count here; the repo-scoped one reports the item\n\t\t// total, which no cap is derived from. Latched even when SDK metadata explains the page: a composite page\n\t\t// reports a failed or cursorless sibling facet there, which says nothing about the cap another reached.\n\t\tif (accountWide && pageTruncated && value.paging?.totalCount != null) {\n\t\t\ttruncatedTotalCount = Math.max(truncatedTotalCount ?? 0, value.paging.totalCount);\n\t\t}\n\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\t// A read that can't confirm completeness (single-page provider reads with no `hasNextPage`)\n\t\t\t// sets `paging.truncated`; propagate it (and any top-level `truncated` and SDK incompleteness)\n\t\t\t// so the sweep doesn't claim an all-pages result.\n\t\t\tappendTruncationWarnings(fetchFailed ? 'interrupted' : 'exhausted');\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\t// Resolve the continuation BEFORE deciding why the drain stops. `page-budget` claims the missing items\n\t\t// are reachable, which is only true with a usable cursor in hand — and a provider can report another\n\t\t// page while handing back none (Bitbucket Server does, when it omits `nextPageStart`). Checking the\n\t\t// budget first would label that unreachable tail as merely unfetched, so raising `maxPages` would\n\t\t// return the identical set.\n\t\tconst nextCursor = value.paging?.cursor;\n\t\t// A cursor already used isn't a continuation either — following it refetches a page we have, and a\n\t\t// provider that cycles (A→B→A) would otherwise burn the whole budget and then be reported as merely\n\t\t// out of budget. Tracked as a SET rather than compared one-back, matching the SDK's own `followCursors`:\n\t\t// `drainToRequestedPage` and `collectProviderPagedResult` compare only the previous cursor, which a\n\t\t// cycle slips past. That is tolerable there and not here, because only this drain reports `page-budget`.\n\t\tconst continuable = nextCursor != null && nextCursor !== '{}' && !seenCursors.has(nextCursor);\n\t\tif (!continuable || page >= maxPages) {\n\t\t\t// A usable cursor in hand means `page-budget`, even when an earlier page was capped: a composite\n\t\t\t// read caps its facets independently, so one facet reaching the provider's ceiling leaves its\n\t\t\t// siblings' cursors live, and raising the budget returns more of them. What no budget returns is the\n\t\t\t// capped part, so that is reported BESIDE the budget stop rather than in place of it — unless an SDK\n\t\t\t// omission already says so. Neither warning then misstates the part the other describes.\n\t\t\tconst budgetStop = !fetchFailed && continuable;\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\ttruncationWarning(\n\t\t\t\t\tid,\n\t\t\t\t\tdomain,\n\t\t\t\t\tconnectionId,\n\t\t\t\t\t'Pull request',\n\t\t\t\t\tfetchFailed ? 'interrupted' : budgetStop ? 'page-budget' : 'exhausted',\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst cap = capWarning();\n\t\t\tif (cap != null) {\n\t\t\t\tappendDedupedWarning(warnings, cap);\n\t\t\t} else if (budgetStop && unexplainedTruncation) {\n\t\t\t\tappendDedupedWarning(warnings, cappedBesideBudgetWarning(id, domain, connectionId, 'Pull request'));\n\t\t\t}\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: true,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\tseenCursors.add(nextCursor);\n\t\tcursor = nextCursor;\n\t}\n}\n\n/**\n * Drains every page of repositories under an org for one git-host integration, threading the opaque\n * next-cursor the provider returns. Stops at `maxPages` (marking `truncated`) or on a hard read failure\n * (marking `fetchFailed`), keeping the pages fetched so far.\n */\nexport async function drainRepositories(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\torg: string,\n\tproject: string | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n): Promise<{\n\trepos: ProviderRepository[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n}> {\n\tconst repos: ProviderRepository[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\n\tfor (;;) {\n\t\tpage++;\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t() =>\n\t\t\t\tintegration.getRepositoriesForOrgResult(org, {\n\t\t\t\t\tproject: project,\n\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: true },\n\t\t);\n\t\tif (warning != null) {\n\t\t\t// Deduped, like every other accumulation here: a soft warning (`{ value, error }`) repeats verbatim on\n\t\t\t// each page that hits the same condition, and the array's contract is that no two entries are equal.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\tconst interruptedAfterProgress = page > 1;\n\t\t\tif (interruptedAfterProgress && warning == null) {\n\t\t\t\t// Not a backstop: the read was cut short mid-drain, which is why it also sets `fetchFailed`\n\t\t\t\t// below. A retry may complete it, so this must not claim the succeeded-but-capped omission.\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(id, domain, connectionId, 'Repository', 'interrupted'),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst failed = fetchFailed || warning != null || interruptedAfterProgress;\n\t\t\t// An SDK omission from an earlier page asserts the read succeeded; this one didn't.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || interruptedAfterProgress,\n\t\t\t};\n\t\t}\n\n\t\trepos.push(...value.values);\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\ttruncated = truncated || value.truncated === true || value.paging?.truncated === true || assessment.truncated;\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t};\n\t\t}\n\n\t\tif (page >= maxPages) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tconst nextCursor = value.paging?.cursor;\n\t\tif (nextCursor == null || nextCursor === '{}') {\n\t\t\t// Provider says there is more but didn't return a usable cursor; stop rather than refetch the same page.\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tcursor = nextCursor;\n\t}\n}\n\nexport function resolvePullRequestSweepTargets(options: PullRequestSweepOptions | undefined): {\n\ttargets: readonly ProviderSweepTarget[];\n\tattributeUnavailableProviders: boolean;\n} {\n\tif (options?.targets != null) {\n\t\tif (options.providerIds != null || options.connectionId != null) {\n\t\t\tthrow new TypeError(\"Pull request sweep 'targets' cannot be combined with 'providerIds' or 'connectionId'\");\n\t\t}\n\n\t\tconst seenProviderIds = new Set<IntegrationIds>();\n\t\tfor (const target of options.targets) {\n\t\t\tif (seenProviderIds.has(target.providerId)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`Pull request sweep targets must contain at most one target per provider; duplicate '${target.providerId}'`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tseenProviderIds.add(target.providerId);\n\t\t}\n\n\t\treturn { targets: options.targets, attributeUnavailableProviders: true };\n\t}\n\n\tconst providerIds =\n\t\toptions?.providerIds ?? supportedOrderedCloudIntegrationIds.filter(id => !isIssuesHostIntegrationId(id));\n\tconst connectionId = providerIds.length === 1 ? options?.connectionId : undefined;\n\treturn {\n\t\ttargets: providerIds.map(providerId => ({ providerId: providerId, connectionId: connectionId })),\n\t\tattributeUnavailableProviders: options?.providerIds != null,\n\t};\n}\n\nexport async function getCurrentAccountIdentity(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n): Promise<{ id: string; username?: string } | undefined> {\n\ttry {\n\t\tconst account = await integration.getCurrentAccount({ connectionId: connectionId });\n\t\treturn account != null ? { id: account.id, username: account.username } : undefined;\n\t} catch {\n\t\t// Authorship is optional enrichment; don't turn a successful PR read into a failure if identity lookup fails.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * The current account to map each raw row against, in order (see {@link GitHostIntegration.getPullRequestViewers}).\n * Rows that name no one can't be matched, so a read of only those resolves nothing; a failed lookup leaves every\n * row's authorship unknown rather than failing the read.\n */\nexport async function getPullRequestViewers(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n\tpullRequests: readonly (ProviderPullRequest | PullRequest)[],\n): Promise<({ id: string; username?: string } | null | undefined)[]> {\n\tif (!pullRequests.some(pullRequestNamesPeople)) return pullRequests.map(() => undefined);\n\n\ttry {\n\t\treturn await integration.getPullRequestViewers(pullRequests, connectionId);\n\t} catch {\n\t\treturn pullRequests.map(() => undefined);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"drains.js","names":[],"sources":["../../../../../plus/integrations/src/reads/drains.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport type { PullRequest, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { defaultPullRequestSort } from '@gitlens/git/models/pullRequest.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { supportedOrderedCloudIntegrationIds } from '../constants.js';\nimport type { ProviderSweepTarget, PullRequestSweepOptions } from '../manager.js';\nimport type { GitHostIntegration } from '../models/gitHostIntegration.js';\nimport type { IntegrationResult } from '../models/integration.js';\nimport type { IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { PullRequestFilter } from '../providerFilters.js';\nimport type { ProviderPullRequest, ProviderReposInput, ProviderRepository } from '../providers/models.js';\nimport { getProviderPullRequestIdentity, pullRequestNamesPeople } from '../providers/models.js';\nimport type { ProviderWarning } from '../results.js';\nimport { appendDedupedWarning, reconcileOmissionsWithFailure, toProviderWarning } from '../results.js';\nimport { hostFromDomain } from '../utils/domain.utils.js';\nimport { isIssuesHostIntegrationId, isSelfManagedHostIntegrationId } from '../utils/integration.utils.js';\nimport {\n\tcappedBesideBudgetWarning,\n\tnoConnectionWarning,\n\tpullRequestSearchCapResultWarning,\n\ttruncationWarning,\n} from './warnings.js';\n\n/**\n * The all-pages reads: a provider read run repeatedly until it runs out of pages, as opposed to the single-page\n * reads the facade's paged surface exposes. `sweepPullRequests` and `broadenIssues` are built on these.\n *\n * A drain's job is to keep going where a single page would stop, so what distinguishes it is its failure\n * bookkeeping rather than its loop: it must return the prefix it did read plus enough signal for the caller to\n * know the result is not authoritative — `truncated` when pages remain unread (a page backstop, or a provider\n * claiming another page while handing back no usable cursor), `fetchFailed` when a page or scope failed\n * outright. Discarding the prefix on a late failure, or returning it as if complete, are the two mistakes these\n * exist to prevent.\n */\n\n/**\n * Runs a result-returning read and captures failure as a neutral {@link ProviderWarning} rather than\n * letting it throw or silently vanish. Handles both a returned `{ error }` (the read cores' contract)\n * and a hard throw; a soft warning (`{ value, error }`) yields the value *and* a warning.\n */\nexport async function runCaptured<T>(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfn: () => Promise<IntegrationResult<T>>,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: T; warning?: ProviderWarning }> {\n\ttry {\n\t\tconst result = await fn();\n\t\tif (result == null) {\n\t\t\t// The read core returns undefined only when it couldn't resolve a session. For a per-connection\n\t\t\t// or explicit-domain read that means the requested target is gone or its authentication is invalid,\n\t\t\t// which must not be reported as an empty account. The untargeted primary path legitimately yields\n\t\t\t// nothing when the provider isn't connected, so leave it as an empty result.\n\t\t\treturn connectionId != null || options?.warnOnMissingSession\n\t\t\t\t? { warning: noConnectionWarning(id, domain, connectionId) }\n\t\t\t\t: {};\n\t\t}\n\t\tif (result.error != null) {\n\t\t\treturn { value: result.value, warning: toProviderWarning(id, domain, connectionId, result.error) };\n\t\t}\n\t\treturn { value: result.value };\n\t} catch (ex) {\n\t\treturn { warning: toProviderWarning(id, domain, connectionId, ex) };\n\t}\n}\n\n/**\n * {@link runCaptured} over an issue tracker's resource (site, organization, board) discovery. A read that resolved a\n * session yet answered with no data and no error carries `completeness: 'unknown'` metadata, so it reads as an\n * incomplete discovery rather than as an account with no resources, which `runCaptured` alone can't tell apart from\n * an untargeted read with no session.\n */\nexport async function discoverResources(\n\tintegration: IssuesIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\toptions?: { warnOnMissingSession?: boolean },\n): Promise<{ value?: ResourceDescriptor[]; warning?: ProviderWarning; metadata?: CollectionMetadata }> {\n\tlet noData = false;\n\tconst captured = await runCaptured(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tasync () => {\n\t\t\tconst result = await integration.getResourcesForUserResult(connectionId);\n\t\t\tnoData = result != null && result.error == null && result.value == null;\n\t\t\treturn result;\n\t\t},\n\t\toptions,\n\t);\n\treturn noData ? { ...captured, metadata: { completeness: 'unknown' } } : captured;\n}\n\n/**\n * Drains every page of the user's pull requests for one git-host integration, threading the opaque\n * next-cursor the provider returns (so it works for both page- and cursor-based hosts). Stops at\n * `maxPages` (marking `truncated`) or on a hard read failure (marking `fetchFailed`), keeping the\n * pages fetched so far. A soft warning (`{ value, error }`) is recorded but the drain continues.\n */\nexport async function drainPullRequests(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\trepos: ProviderReposInput,\n\tstate: PullRequestStateFilter[] | undefined,\n\tfilters: PullRequestFilter[] | undefined,\n\t/**\n\t * The sweep options the drain reads beyond its query: the breadth opt-ins only the account-wide read can\n\t * express, and the repo-scoped read's fan-out bound and clone-URL lookup. Grouped rather than passed\n\t * positionally because the opt-ins are same-typed neighbors a transposition would silently swap, and picked\n\t * from the sweep options rather than restated so a new one is declared once.\n\t */\n\tsweepOptions:\n\t\t| Pick<\n\t\t\t\tPullRequestSweepOptions,\n\t\t\t\t'includeReviewRequested' | 'includeReviews' | 'scopeConcurrency' | 'includeRemoteInfo'\n\t\t >\n\t\t| undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n\tattributeUnavailableProvider: boolean,\n): Promise<{\n\titems: (ProviderPullRequest | PullRequest)[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tfailedProvider: boolean;\n}> {\n\tconst items: (ProviderPullRequest | PullRequest)[] = [];\n\tconst itemIndexByIdentity = new Map<string, number>();\n\tconst warnings: ProviderWarning[] = [];\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\t// SDK metadata failures across pages mean the collection is incomplete even when no page threw; carry\n\t// this through the terminal returns instead of resetting it to false at the last page.\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\t// A page the provider capped or couldn't vouch for, and which no other warning already explains — decides\n\t// whether this drain raises one of its own for that part.\n\tlet unexplainedTruncation = false;\n\t// The largest match count reported on a truncated page, from which the drain derives the provider's cap.\n\tlet truncatedTotalCount: number | undefined;\n\tconst capWarning = (): ProviderWarning | undefined =>\n\t\tpullRequestSearchCapResultWarning(id, domain, connectionId, truncatedTotalCount, defaultPullRequestSort);\n\tconst appendTruncationWarnings = (cause: 'interrupted' | 'exhausted'): void => {\n\t\tconst cap = capWarning();\n\t\tif (cap != null) {\n\t\t\tappendDedupedWarning(warnings, cap);\n\t\t}\n\t\t// A cap explains the missing tail of a read that succeeded; a failed read still owes its own warning.\n\t\tif (unexplainedTruncation && (cap == null || cause === 'interrupted')) {\n\t\t\tappendDedupedWarning(warnings, truncationWarning(id, domain, connectionId, 'Pull request', cause));\n\t\t}\n\t};\n\n\t// With no repos this is an account-wide \"my PRs\" sweep. The repo-scoped core rejects an empty `repos`\n\t// input, so read the provider-native account-wide core instead.\n\tconst accountWide = repos.length === 0;\n\t// The breadth opt-ins are only expressible on the account-wide core, so gate them here — the single place\n\t// that already knows which core it is about to call.\n\tconst includeReviewRequested = accountWide && (sweepOptions?.includeReviewRequested ?? false);\n\tconst includeReviews = accountWide && (sweepOptions?.includeReviews ?? false);\n\t/** Every cursor already followed, so a provider that cycles them can't keep the drain walking in circles. */\n\tconst seenCursors = new Set<string>();\n\n\tfor (;;) {\n\t\tpage++;\n\t\t// Snapshot the mutable loop cursor so the read closure doesn't capture a later-reassigned value.\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(id, domain, connectionId, () =>\n\t\t\taccountWide\n\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\t\t\tincludeReviewRequested: includeReviewRequested,\n\t\t\t\t\t\t\tfilters: filters,\n\t\t\t\t\t\t\t// Lite by default; the full projection (reviews) only when a caller opts in.\n\t\t\t\t\t\t\tsummary: !includeReviews,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t)\n\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\trepos,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstate: state,\n\t\t\t\t\t\t\tfilters: filters,\n\t\t\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\t\t\tscopeConcurrency: sweepOptions?.scopeConcurrency,\n\t\t\t\t\t\t\tincludeRemoteInfo: sweepOptions?.includeRemoteInfo,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t),\n\t\t);\n\t\tif (warning != null) {\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\t// An implicit sweep may silently skip a provider that has no session before it yields data.\n\t\t\t// Once a page has been returned, however, losing that session leaves an unread tail and must\n\t\t\t// be attributed even when the provider wasn't explicitly requested.\n\t\t\tconst sessionLostAfterProgress = warning == null && page > 1;\n\t\t\tconst unavailable = warning == null && (attributeUnavailableProvider || sessionLostAfterProgress);\n\t\t\tif (unavailable) {\n\t\t\t\tappendDedupedWarning(warnings, noConnectionWarning(id, domain, connectionId));\n\t\t\t}\n\t\t\t// `warning` set → a hard read failure (incomplete items); otherwise not connected / no session.\n\t\t\tconst failed = fetchFailed || warning != null || unavailable;\n\t\t\t// A cap seen on an earlier page still left results out, so it is still worth saying — but as part\n\t\t\t// of a read that failed, never as an omission. (`failed` is always true here when anything was\n\t\t\t// latched: reaching this exit past page 1 means a later page was lost.)\n\t\t\tappendTruncationWarnings(failed ? 'interrupted' : 'exhausted');\n\t\t\t// An earlier page may already have emitted an omission before this one died; it asserts the read\n\t\t\t// succeeded, which is no longer true.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || sessionLostAfterProgress,\n\t\t\t\t// Only a top-level first-page rejection means the provider itself failed. A later-page or\n\t\t\t\t// per-scope failure still yielded a usable provider slice and stays represented separately.\n\t\t\t\tfailedProvider: page === 1 && (warning != null || unavailable),\n\t\t\t};\n\t\t}\n\n\t\t// Composite account-wide queries can surface the same PR on different relationship/state pages\n\t\t// (for example authored + review-requested, or closed + merged). Keep the first stable position but\n\t\t// replace its value with the latest representation so a later, richer merged state wins. Repository\n\t\t// identity is what keys a row (see `getProviderPullRequestIdentity`), falling back to the canonical\n\t\t// URL; a row with neither stays unkeyed and is retained so unrelated incomplete rows are never\n\t\t// collapsed.\n\t\tfor (const pullRequest of value.values) {\n\t\t\tconst identity = getProviderPullRequestIdentity(pullRequest);\n\t\t\tif (identity == null) {\n\t\t\t\titems.push(pullRequest);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst existingIndex = itemIndexByIdentity.get(identity);\n\t\t\tif (existingIndex == null) {\n\t\t\t\titemIndexByIdentity.set(identity, items.length);\n\t\t\t\titems.push(pullRequest);\n\t\t\t} else {\n\t\t\t\titems[existingIndex] = pullRequest;\n\t\t\t}\n\t\t}\n\n\t\t// Assess this page's SDK metadata: append scope-aware warnings (deduped across pages), and remember\n\t\t// whether a structured failure or incompleteness occurred.\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\tconst pageTruncated =\n\t\t\t(value as { truncated?: boolean }).truncated === true ||\n\t\t\tvalue.paging?.truncated === true ||\n\t\t\tassessment.truncated;\n\t\ttruncated = truncated || pageTruncated;\n\t\t// Whether this drain owes its OWN warning for a capped page — `mergeAssessmentInto` has already appended\n\t\t// one when the fact came from SDK metadata, and repeating it would be noise.\n\t\tunexplainedTruncation = unexplainedTruncation || (pageTruncated && !assessment.truncated);\n\t\t// Only the account-wide read reports a search match count here; the repo-scoped one reports the item\n\t\t// total, which no cap is derived from. Latched even when SDK metadata explains the page: a composite page\n\t\t// reports a failed or cursorless sibling facet there, which says nothing about the cap another reached.\n\t\tif (accountWide && pageTruncated && value.paging?.totalCount != null) {\n\t\t\ttruncatedTotalCount = Math.max(truncatedTotalCount ?? 0, value.paging.totalCount);\n\t\t}\n\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\t// A read that can't confirm completeness (single-page provider reads with no `hasNextPage`)\n\t\t\t// sets `paging.truncated`; propagate it (and any top-level `truncated` and SDK incompleteness)\n\t\t\t// so the sweep doesn't claim an all-pages result.\n\t\t\tappendTruncationWarnings(fetchFailed ? 'interrupted' : 'exhausted');\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\t// Resolve the continuation BEFORE deciding why the drain stops. `page-budget` claims the missing items\n\t\t// are reachable, which is only true with a usable cursor in hand — and a provider can report another\n\t\t// page while handing back none (Bitbucket Server does, when it omits `nextPageStart`). Checking the\n\t\t// budget first would label that unreachable tail as merely unfetched, so raising `maxPages` would\n\t\t// return the identical set.\n\t\tconst nextCursor = value.paging?.cursor;\n\t\t// A cursor already used isn't a continuation either — following it refetches a page we have, and a\n\t\t// provider that cycles (A→B→A) would otherwise burn the whole budget and then be reported as merely\n\t\t// out of budget. Tracked as a SET rather than compared one-back, matching the SDK's own `followCursors`:\n\t\t// `drainToRequestedPage` and `collectProviderPagedResult` compare only the previous cursor, which a\n\t\t// cycle slips past. That is tolerable there and not here, because only this drain reports `page-budget`.\n\t\tconst continuable = nextCursor != null && nextCursor !== '{}' && !seenCursors.has(nextCursor);\n\t\tif (!continuable || page >= maxPages) {\n\t\t\t// A usable cursor in hand means `page-budget`, even when an earlier page was capped: a composite\n\t\t\t// read caps its facets independently, so one facet reaching the provider's ceiling leaves its\n\t\t\t// siblings' cursors live, and raising the budget returns more of them. What no budget returns is the\n\t\t\t// capped part, so that is reported BESIDE the budget stop rather than in place of it — unless an SDK\n\t\t\t// omission already says so. Neither warning then misstates the part the other describes.\n\t\t\tconst budgetStop = !fetchFailed && continuable;\n\t\t\tappendDedupedWarning(\n\t\t\t\twarnings,\n\t\t\t\ttruncationWarning(\n\t\t\t\t\tid,\n\t\t\t\t\tdomain,\n\t\t\t\t\tconnectionId,\n\t\t\t\t\t'Pull request',\n\t\t\t\t\tfetchFailed ? 'interrupted' : budgetStop ? 'page-budget' : 'exhausted',\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst cap = capWarning();\n\t\t\tif (cap != null) {\n\t\t\t\tappendDedupedWarning(warnings, cap);\n\t\t\t} else if (budgetStop && unexplainedTruncation) {\n\t\t\t\tappendDedupedWarning(warnings, cappedBesideBudgetWarning(id, domain, connectionId, 'Pull request'));\n\t\t\t}\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\titems: items,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: true,\n\t\t\t\tfailedProvider: false,\n\t\t\t};\n\t\t}\n\n\t\tseenCursors.add(nextCursor);\n\t\tcursor = nextCursor;\n\t}\n}\n\n/**\n * Drains every page of repositories under an org for one git-host integration, threading the opaque\n * next-cursor the provider returns. Stops at `maxPages` (marking `truncated`) or on a hard read failure\n * (marking `fetchFailed`), keeping the pages fetched so far.\n */\nexport async function drainRepositories(\n\tintegration: GitHostIntegration,\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\torg: string,\n\tproject: string | undefined,\n\tconnectionId: string | undefined,\n\tmaxPages: number,\n): Promise<{\n\trepos: ProviderRepository[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n}> {\n\tconst repos: ProviderRepository[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tlet cursor: string | undefined;\n\tlet page = 0;\n\n\tfor (;;) {\n\t\tpage++;\n\t\tconst pageCursor = cursor;\n\t\tconst { value, warning } = await runCaptured(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t() =>\n\t\t\t\tintegration.getRepositoriesForOrgResult(org, {\n\t\t\t\t\tproject: project,\n\t\t\t\t\tcursor: pageCursor,\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t}),\n\t\t\t{ warnOnMissingSession: true },\n\t\t);\n\t\tif (warning != null) {\n\t\t\t// Deduped, like every other accumulation here: a soft warning (`{ value, error }`) repeats verbatim on\n\t\t\t// each page that hits the same condition, and the array's contract is that no two entries are equal.\n\t\t\tappendDedupedWarning(warnings, warning);\n\t\t}\n\t\tif (value == null) {\n\t\t\tconst interruptedAfterProgress = page > 1;\n\t\t\tif (interruptedAfterProgress && warning == null) {\n\t\t\t\t// Not a backstop: the read was cut short mid-drain, which is why it also sets `fetchFailed`\n\t\t\t\t// below. A retry may complete it, so this must not claim the succeeded-but-capped omission.\n\t\t\t\tappendDedupedWarning(\n\t\t\t\t\twarnings,\n\t\t\t\t\ttruncationWarning(id, domain, connectionId, 'Repository', 'interrupted'),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst failed = fetchFailed || warning != null || interruptedAfterProgress;\n\t\t\t// An SDK omission from an earlier page asserts the read succeeded; this one didn't.\n\t\t\treconcileOmissionsWithFailure(warnings, failed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: failed,\n\t\t\t\ttruncated: truncated || interruptedAfterProgress,\n\t\t\t};\n\t\t}\n\n\t\trepos.push(...value.values);\n\t\tconst assessment = mergeAssessmentInto(warnings, id, domain, connectionId, value.metadata);\n\t\tfetchFailed = fetchFailed || assessment.fetchFailed;\n\t\ttruncated = truncated || value.truncated === true || value.paging?.truncated === true || assessment.truncated;\n\t\tif (!(value.paging?.more ?? false)) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn {\n\t\t\t\trepos: repos,\n\t\t\t\twarnings: warnings,\n\t\t\t\tfetchFailed: fetchFailed,\n\t\t\t\ttruncated: truncated,\n\t\t\t};\n\t\t}\n\n\t\tif (page >= maxPages) {\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tconst nextCursor = value.paging?.cursor;\n\t\tif (nextCursor == null || nextCursor === '{}') {\n\t\t\t// Provider says there is more but didn't return a usable cursor; stop rather than refetch the same page.\n\t\t\treconcileOmissionsWithFailure(warnings, fetchFailed);\n\t\t\treturn { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };\n\t\t}\n\n\t\tcursor = nextCursor;\n\t}\n}\n\nfunction describeSweepTargetSelector(target: ProviderSweepTarget): string {\n\tconst parts: string[] = [];\n\tif (target.connectionId != null) {\n\t\tparts.push(`connectionId '${target.connectionId}'`);\n\t}\n\tif (target.domain != null) {\n\t\tparts.push(`domain '${target.domain}'`);\n\t}\n\treturn parts.length !== 0 ? ` (${parts.join(', ')})` : '';\n}\n\nexport function resolvePullRequestSweepTargets(options: PullRequestSweepOptions | undefined): {\n\ttargets: readonly ProviderSweepTarget[];\n\tattributeUnavailableProviders: boolean;\n} {\n\tif (options?.targets != null) {\n\t\tif (options.providerIds != null || options.connectionId != null) {\n\t\t\tthrow new TypeError(\"Pull request sweep 'targets' cannot be combined with 'providerIds' or 'connectionId'\");\n\t\t}\n\n\t\t// Keyed by the selector as the read resolves it, so a repeat is a duplicate however its domain is spelled.\n\t\t// Several targets of one provider are legitimate (one per connection), but each must name its connection:\n\t\t// an unnamed one reads the primary connection, which may be one of the named ones. A domain names a host\n\t\t// only on a self-managed provider; a cloud provider's read ignores it.\n\t\tconst seenTargets = new Set<string>();\n\t\tconst targetCounts = new Map<IntegrationIds, number>();\n\t\tconst unnamedTargets: ProviderSweepTarget[] = [];\n\t\tfor (const target of options.targets) {\n\t\t\tconst host = isSelfManagedHostIntegrationId(target.providerId) ? hostFromDomain(target.domain) : undefined;\n\t\t\tconst key = JSON.stringify([target.providerId, target.connectionId ?? '', host ?? '']);\n\t\t\tif (seenTargets.has(key)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`Pull request sweep targets must be unique; duplicate '${target.providerId}' target${describeSweepTargetSelector(target)}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tseenTargets.add(key);\n\t\t\ttargetCounts.set(target.providerId, (targetCounts.get(target.providerId) ?? 0) + 1);\n\t\t\tif (target.connectionId == null && host == null) {\n\t\t\t\tunnamedTargets.push(target);\n\t\t\t}\n\t\t}\n\n\t\tfor (const target of unnamedTargets) {\n\t\t\tif ((targetCounts.get(target.providerId) ?? 0) > 1) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`Pull request sweep targets for '${target.providerId}' must each name a connectionId or domain when there is more than one (a domain only on a self-managed host); an unnamed target reads the primary connection`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn { targets: options.targets, attributeUnavailableProviders: true };\n\t}\n\n\tconst providerIds =\n\t\toptions?.providerIds ?? supportedOrderedCloudIntegrationIds.filter(id => !isIssuesHostIntegrationId(id));\n\tconst connectionId = providerIds.length === 1 ? options?.connectionId : undefined;\n\treturn {\n\t\ttargets: providerIds.map(providerId => ({ providerId: providerId, connectionId: connectionId })),\n\t\tattributeUnavailableProviders: options?.providerIds != null,\n\t};\n}\n\nexport async function getCurrentAccountIdentity(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n): Promise<{ id: string; username?: string } | undefined> {\n\ttry {\n\t\tconst account = await integration.getCurrentAccount({ connectionId: connectionId });\n\t\treturn account != null ? { id: account.id, username: account.username } : undefined;\n\t} catch {\n\t\t// Authorship is optional enrichment; don't turn a successful PR read into a failure if identity lookup fails.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * The current account to map each raw row against, in order (see {@link GitHostIntegration.getPullRequestViewers}).\n * Rows that name no one can't be matched, so a read of only those resolves nothing; a failed lookup leaves every\n * row's authorship unknown rather than failing the read.\n */\nexport async function getPullRequestViewers(\n\tintegration: GitHostIntegration,\n\tconnectionId: string | undefined,\n\tpullRequests: readonly (ProviderPullRequest | PullRequest)[],\n): Promise<({ id: string; username?: string } | null | undefined)[]> {\n\tif (!pullRequests.some(pullRequestNamesPeople)) return pullRequests.map(() => undefined);\n\n\ttry {\n\t\treturn await integration.getPullRequestViewers(pullRequests, connectionId);\n\t} catch {\n\t\treturn pullRequests.map(() => undefined);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import { mapBounded } from "../../../utils/promise.js";
|
|
|
2
2
|
import { supportedOrderedCloudIntegrationIds, supportedOrderedCloudIssuesIntegrationIds } from "../constants.js";
|
|
3
3
|
import { appendDedupedWarning } from "../results.js";
|
|
4
4
|
import { mergeAssessmentInto } from "../collectionMetadata.js";
|
|
5
|
-
import { isGitHostIntegration, isIssuesHostIntegrationId, isLinearIntegration, warnOnMissingSessionForDomain } from "../utils/integration.utils.js";
|
|
6
5
|
import { toProviderRepositoryShape } from "../providers/models.js";
|
|
6
|
+
import { isGitHostIntegration, isIssuesHostIntegrationId, isLinearIntegration, warnOnMissingSessionForDomain } from "../utils/integration.utils.js";
|
|
7
7
|
import { isIssuesIntegration } from "../models/issuesIntegration.js";
|
|
8
8
|
import { pageToCursor, refusedPage, resolveContinuation, resolveCurrentPage, toProviderPageInfo } from "./paging.js";
|
|
9
9
|
import { gitHostOnlySurfaceWarning, truncationWarning, unsupportedWarning } from "./warnings.js";
|
|
@@ -24,7 +24,7 @@ import { ProviderReadContext } from "./context.js";
|
|
|
24
24
|
* ONE integration call per invocation when no target carries an `etag`, whatever the target count, as in
|
|
25
25
|
* `getPullRequestsBatch`: the integration fans its targets out (chunked for GitHub/GHE, one request per target with
|
|
26
26
|
* bounded concurrency on GitLab and Azure DevOps and on the trackers) and settles each independently, so failing
|
|
27
|
-
* targets spend at most one strike of the
|
|
27
|
+
* targets spend at most one strike of the connection's failure budget.
|
|
28
28
|
*
|
|
29
29
|
* With etags, up to THREE on a host with a cheap check (GitHub/GHE, GitLab and Azure DevOps; Jira Cloud and Linear
|
|
30
30
|
* among the trackers): the cheap check of the targets that carry one, a full read of the rest started alongside it,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issueBatch.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueBatch.ts"],"sourcesContent":["import type { IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IntegrationIds, IssuesHostIntegrationIds } from '../constants.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport type { IssueEtagInclude } from '../models/integration.js';\nimport { issueEtagIncludes } from '../models/integration.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { githubGraphQLInt32Max, isAzureProviderId, isGitHubProviderId } from '../providers/providerErrors.js';\nimport type { ProviderResult, ProviderWarning } from '../results.js';\nimport { areDomainsOnSameHost, hostFromDomain } from '../utils/domain.utils.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\tisIssuesSelfManagedHostIntegrationId,\n} from '../utils/integration.utils.js';\nimport type { EtaggedBatchRow } from './batchEtags.js';\nimport { readEtaggedBatch } from './batchEtags.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport {\n\tfindInvalidIssueEtagInclude,\n\tissueEtag,\n\tissueEtagFieldsFromShape,\n\tnormalizeIssueEtagIncludes,\n} from './etag.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tissuesUnsupportedWarning,\n\tissueTrackerOnlySurfaceWarning,\n\tnoConnectionWarning,\n\totherWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\n/**\n * The BATCH issue read: resolve N issues BY IDENTITY in one call — `(owner, repo, number)` coordinates on a git\n * host, `(resourceId, ABC-123)` identifiers on an issue tracker.\n *\n * A sibling of the issue searches rather than a mode of one, because it answers a different question. A search\n * asks \"what matches\"; this asks \"does this exact issue exist\", which is what a caller correlating a branch name\n * to the issue it references is actually asking. Three consequences follow, and they are the reason this exists:\n *\n * - It resolves by EXACT IDENTIFIER, so relevance never enters into it.\n * - No result ceiling applies, so there is no partial window to reason about and no omission to report.\n * - An absent slot is a PROVEN ABSENCE, not \"not found within a page budget\". That is the property that matters\n * most: a caller can CACHE a miss. Emulating this with a paged list cannot prove absence without walking the\n * whole scope, so a miss stays unproven and the walk repeats on every pass, forever.\n *\n * Modeled on `countIssues` for its shape — caller-owned `key` echoed back, per-target isolation, as few requests\n * as the provider allows — because both take a set of independent questions and answer them together.\n *\n * ONE integration call per invocation when no target carries an `etag`, whatever the target count, as in\n * `getPullRequestsBatch`: the integration fans its targets out (chunked for GitHub/GHE, one request per target with\n * bounded concurrency on GitLab and Azure DevOps and on the trackers) and settles each independently, so failing\n * targets spend at most one strike of the integration's failure budget.\n *\n * With etags, up to THREE on a host with a cheap check (GitHub/GHE, GitLab and Azure DevOps; Jira Cloud and Linear\n * among the trackers): the cheap check of the targets that carry one, a full read of the rest started alongside it,\n * and a full read of the targets whose etag no longer matches — see `readEtaggedBatch`. Every other tracker still makes\n * one call and etags its rows.\n */\n\n/** One issue to resolve, echoed back under the caller's own `key`. Which form a call takes depends on its provider. */\nexport type IssueBatchTarget =\n\t/**\n\t * A git host's issue, by repository coordinate. GitHub/GHE, GitLab (and self-managed) and Azure DevOps (and\n\t * Server); Bitbucket and Bitbucket Data Center have no issues and refuse.\n\t */\n\t| {\n\t\t\tkey: string;\n\t\t\t/** GitHub owner, GitLab namespace path (may be nested), or Azure DevOps organization (or collection). */\n\t\t\towner: string;\n\t\t\t/**\n\t\t\t * Repository name. Required, but ignored on Azure DevOps, whose work items belong to the project rather\n\t\t\t * than to a repository; there it only names the project of a project-less Azure DevOps Server path read\n\t\t\t * behind a virtual directory (`{vdir}/{collection}/_git/{repo}` as owner `{vdir}`, project `{collection}`).\n\t\t\t */\n\t\t\trepo: string;\n\t\t\t/** Issue number, GitLab issue iid, or Azure DevOps work item id. */\n\t\t\tnumber: number;\n\t\t\t/** Azure DevOps project. Required there, ignored elsewhere. */\n\t\t\tproject?: string;\n\t\t\t/** The {@link IssueBatchResult.etag} of the copy the caller holds; see {@link IssueBatchResult}. */\n\t\t\tetag?: string;\n\t }\n\t/**\n\t * An issue tracker's issue, by its own identifier (e.g. `ABC-123`) within a resource. Jira (Cloud and Data\n\t * Center) and Linear. For Jira Data Center `resourceId` is the host — the instance's single resource, as\n\t * `listOrgs` reports it — and `resourceUrl` is not needed, since the browser link is built from the\n\t * connection's own base URL; Jira Cloud requires it.\n\t */\n\t| {\n\t\t\tkey: string;\n\t\t\tresourceId: string;\n\t\t\tresourceUrl?: string;\n\t\t\tidentifier: string;\n\t\t\t/**\n\t\t\t * The {@link IssueBatchResult.etag} of the copy the caller holds. Checked cheaply on Jira Cloud and Linear;\n\t\t\t * every other tracker accepts it but reads the issue in full.\n\t\t\t */\n\t\t\tetag?: string;\n\t };\n\ntype CoordinateTarget = Extract<IssueBatchTarget, { owner: string }>;\ntype TrackerTarget = Extract<IssueBatchTarget, { resourceId: string }>;\n\n/**\n * The answer for one {@link IssueBatchTarget}, in one of four states:\n * - `{ key, issue, etag }` — read in full. Every fully read row carries an `etag`, whether or not the caller sent\n * one, so a caller can seed its etags from the reads it already makes.\n * - `{ key, unchanged: true, etag }` — the cheap check proved the caller's copy current, and nothing else was\n * fetched. Only for a target that sent an `etag`, on a host with a cheap check.\n * - `{ key }` — PROVEN ABSENT.\n * - no row, with `fetchFailed` and a warning — the read could not check. Never treat that as absent.\n */\nexport interface IssueBatchResult {\n\tkey: string;\n\t/**\n\t * The resolved issue, or `undefined` when it PROVABLY does not exist (or is not visible to this connection) —\n\t * or when it is `unchanged`.\n\t *\n\t * Absent is an answer here, unlike every paged read on this facade: a target that FAILED — its own request\n\t * outright, or just its own alias within an otherwise-answering GitHub chunk (e.g. an org enforcing SAML SSO\n\t * the token isn't authorized for) — is not returned at all and sets `fetchFailed`, so a caller can tell\n\t * \"proven absent\" from \"unknown\" and cache the first without ever caching the second.\n\t */\n\tissue?: IssueShape;\n\t/**\n\t * Opaque: compare for equality only, never parse. Computed by core from the issue's change state — its state and\n\t * update time, plus each input the call's `etagIncludes` listed — and NOT the provider's HTTP ETag. Send it back\n\t * as the target's `etag`. An etag from another scheme or another `etagIncludes` set simply compares unequal and\n\t * costs a full read, never a false `unchanged`. A host can add a reaction without moving the update time, so\n\t * unless `'reactions'` is listed, an `unchanged` copy's `thumbsUpCount` may be stale.\n\t */\n\tetag?: string;\n\t/** The caller's copy is current (its `etag` matched); `issue` is absent because nothing was read. */\n\tunchanged?: true;\n}\n\nconst surface = 'Batch issue resolution';\n\nfunction refused(warning: ProviderWarning): ProviderResult<IssueBatchResult> {\n\treturn { items: [], warnings: [warning], fetchFailed: true };\n}\n\nexport async function getIssuesBatch(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\ttargets: readonly IssueBatchTarget[];\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t\t/**\n\t\t * Widens every etag to the listed inputs, each of which a host changes without moving the issue's update\n\t\t * time, and each of which costs its own fields in the cheap check. `'reactions'` is the thumbs-up count; it\n\t\t * widens nothing on a host whose rows carry no real count (Azure DevOps work items and the trackers). Order\n\t\t * and repeats don't matter. An unknown value refuses the whole call. Changes only which etag is computed; the\n\t\t * full read is the same. Calls that differ in this set never match each other's etags.\n\t\t */\n\t\tetagIncludes?: readonly IssueEtagInclude[];\n\t},\n): Promise<ProviderResult<IssueBatchResult>> {\n\t// Nothing was asked for, so nothing is missing: an empty success, not a refusal.\n\tif (options.targets.length === 0) return { items: [], warnings: [] };\n\n\tconst duplicateKey = findDuplicateKey(options.targets);\n\tif (duplicateKey != null) {\n\t\t// Refuses the whole call rather than deduping: `key` exists so the caller can match results without\n\t\t// positional bookkeeping, and two results under one key make that ambiguous for EVERY target, not just\n\t\t// the repeated one.\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Duplicate issue batch target key '${duplicateKey}'; keys identify results, so each must be unique.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Same as a bad target: refused whole before any request, naming the value and what's allowed.\n\tconst invalidInclude = findInvalidIssueEtagInclude(options.etagIncludes ?? []);\n\tif (invalidInclude != null) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Unknown issue etag include '${invalidInclude}'; expected one of ${issueEtagIncludes.map(i => `'${i}'`).join(', ')}.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst etagIncludes = normalizeIssueEtagIncludes(options.etagIncludes ?? []);\n\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn getIssuesBatchForTracker(\n\t\t\tctx,\n\t\t\toptions.providerId,\n\t\t\toptions.targets,\n\t\t\toptions.connectionId,\n\t\t\toptions.domain,\n\t\t\tetagIncludes,\n\t\t);\n\t}\n\n\tconst targets = options.targets;\n\tif (!targets.every(isCoordinateTarget)) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} for '${options.providerId}' takes repository coordinates { key, owner, repo, number, project? }, not tracker identifiers.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// The validated value must be the one sent to the provider, so every string field is trimmed once here.\n\tconst trimmedTargets = targets.map(trimCoordinateFields);\n\n\t// Deterministic caller bugs, refused like a duplicate key rather than sent upstream, where a blank owner or repo\n\t// would come back as a missing repository — a proven absence the caller would cache.\n\tconst invalid = findInvalidCoordinateTarget(options.providerId, trimmedTargets);\n\tif (invalid != null) {\n\t\treturn refused(otherWarning(options.providerId, undefined, options.connectionId, invalid));\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\treturn unresolvedIntegration(ctx, options.providerId, options.connectionId, options.domain);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\tif (!integration.supportsIssues) {\n\t\treturn refused(issuesUnsupportedWarning(options.providerId, domain, options.connectionId));\n\t}\n\n\tconst toCoordinate = (t: CoordinateTarget) => ({\n\t\towner: t.owner,\n\t\trepo: t.repo,\n\t\tnumber: t.number,\n\t\tproject: t.project,\n\t});\n\tconst result = await readEtaggedBatch({\n\t\tproviderId: options.providerId,\n\t\tdomain: domain,\n\t\tconnectionId: options.connectionId,\n\t\ttargets: trimmedTargets,\n\t\tsupportsEtags: integration.supportsIssueEtags,\n\t\treadFull: batch =>\n\t\t\trunCaptured(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t() => integration.getIssuesBatchResult(batch.map(toCoordinate), undefined, options.connectionId),\n\t\t\t\t// A missing session must surface as a connection warning, including on the primary path, which\n\t\t\t\t// otherwise reads it as \"not connected\" and says nothing — and this read would then call it\n\t\t\t\t// unsupported.\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\treadEtagFields: batch =>\n\t\t\trunCaptured(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t() =>\n\t\t\t\t\tintegration.getIssuesEtagFieldsResult(\n\t\t\t\t\t\tbatch.map(toCoordinate),\n\t\t\t\t\t\t{ etagIncludes: etagIncludes },\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t),\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\titemEtag: issue => issueEtag(issueEtagFieldsFromShape(issue), etagIncludes),\n\t\tfieldsEtag: fields => issueEtag(fields, etagIncludes),\n\t\t// A provider that doesn't implement the batch hook answers `undefined` with no error. Either way its targets\n\t\t// are DROPPED rather than reported as absent — the difference between \"unknown\" and \"proven absent\" is the\n\t\t// read's whole value, and a failure must never be cached as an answer.\n\t\tunsupportedWarning: () =>\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} is not supported by '${options.providerId}'; resolve issues individually instead.`,\n\t\t\t),\n\t});\n\n\treturn toIssueBatchResult(result);\n}\n\n/**\n * The tracker form. ONE integration call per invocation when no target carries an `etag`, whatever the target count:\n * the integration makes one single-issue request per target, with bounded concurrency, and settles each independently\n * — see `IssuesIntegration.getIssuesByResourceIdBatchResult`. With etags, a tracker with a cheap check (Jira Cloud's\n * bulk fetch, Linear's per-team query) takes the coordinate form's `readEtaggedBatch` flow; every other tracker\n * ignores a target's `etag` and reads and etags every found row in full.\n *\n * `resourceId` is trusted and the read does no resource discovery. It is handed to the provider's by-resource-id\n * read as-is, never wrapped into a synthesized descriptor: Linear and Trello answer `undefined` for a descriptor\n * that fails `isIssueResourceDescriptor`, which would be published as a proven absence.\n *\n * `domain` selects the host of a self-managed tracker (Jira Data Center), as it does for\n * `listIssueTrackerIssuesPage`, and is ignored for the cloud trackers, which have a single canonical host. Unlike\n * the paged reads, this one never falls back to the primary connection for a self-managed tracker: it requires a\n * `domain` or a `connectionId` with a configured host and refuses otherwise. Two self-hosted instances routinely\n * issue the same project and issue keys, and `issue: undefined` is a proven absence a caller may cache — an answer\n * from whichever host happens to be primary would be cached under a key that names a different instance (#5872).\n */\nasync function getIssuesBatchForTracker(\n\tctx: ProviderReadContext,\n\tproviderId: IssuesHostIntegrationIds,\n\ttargets: readonly IssueBatchTarget[],\n\tconnectionId: string | undefined,\n\tdomain: string | undefined,\n\tetagIncludes: readonly IssueEtagInclude[],\n): Promise<ProviderResult<IssueBatchResult>> {\n\tif (!targets.every(isTrackerTarget)) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\tproviderId,\n\t\t\t\tundefined,\n\t\t\t\tconnectionId,\n\t\t\t\t`${surface} for '${providerId}' takes tracker identifiers { key, resourceId, resourceUrl?, identifier }, not repository coordinates.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// The validated value must be the one sent to the provider, so every string field is trimmed once here.\n\tconst trimmedTargets = targets.map(trimTrackerTarget);\n\n\t// Deterministic caller bugs, refused like a duplicate key rather than sent upstream to fail one by one.\n\tconst invalid = findInvalidTrackerTarget(providerId, trimmedTargets);\n\tif (invalid != null) {\n\t\treturn refused(otherWarning(providerId, undefined, connectionId, invalid));\n\t}\n\n\t// A `domain` only selects a host when it parses to one, and a `connectionId` only when it names a configured\n\t// connection that has one; anything else would resolve the primary host instead, which is the fallback this\n\t// read refuses.\n\tif (\n\t\tisIssuesSelfManagedHostIntegrationId(providerId) &&\n\t\t(domain != null\n\t\t\t? hostFromDomain(domain) == null\n\t\t\t: connectionId == null ||\n\t\t\t\t!ctx.getConfigured(providerId).some(c => c.id === connectionId && hostFromDomain(c.domain) != null))\n\t) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\tproviderId,\n\t\t\t\tundefined,\n\t\t\t\tconnectionId,\n\t\t\t\t`${surface} requires a domain or a configured connection id for '${providerId}': every configured host can hold a different issue under the same key, so the read does not answer from the primary connection.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(providerId, connectionId, domain);\n\tif (integration == null) return unresolvedIntegration(ctx, providerId, connectionId, domain);\n\tif (!isIssuesIntegration(integration)) {\n\t\treturn refused(issueTrackerOnlySurfaceWarning(providerId, connectionId, surface));\n\t}\n\n\tconst unsupported = (): ProviderWarning =>\n\t\tunsupportedWarning(\n\t\t\tproviderId,\n\t\t\tundefined,\n\t\t\tconnectionId,\n\t\t\t`${surface} is not supported by '${providerId}'; its single-issue read cannot prove an absence, so a miss would not be safe to cache.`,\n\t\t);\n\tif (!integration.supportsIssueLookupByResourceId) return refused(unsupported());\n\n\t// A self-managed tracker's single resource is its host, and a caller keys (and caches) each answer by\n\t// `resourceId` while the read is addressed to the host `domain`/`connectionId` resolved. When they disagree,\n\t// host B's answer — including a cacheable absence — would be stored under host A's key. Refused whole, like\n\t// the caller bugs above: a target naming another host means the call selected a host its caller did not mean.\n\tif (isIssuesSelfManagedHostIntegrationId(providerId)) {\n\t\tconst mismatched = trimmedTargets.find(t => !areDomainsOnSameHost(t.resourceId, integration.domain));\n\t\tif (mismatched != null) {\n\t\t\treturn refused(\n\t\t\t\totherWarning(\n\t\t\t\t\tproviderId,\n\t\t\t\t\tundefined,\n\t\t\t\t\tconnectionId,\n\t\t\t\t\t`Issue batch target '${mismatched.key}' requires the resource id of '${providerId}' to name the host the read resolved to.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\n\tconst resolvedDomain = ctx.domainForRead(integration, providerId, connectionId, domain);\n\n\tconst toResourceTarget = (t: TrackerTarget) => ({\n\t\tresourceId: t.resourceId,\n\t\tidentifier: t.identifier,\n\t\tresourceUrl: t.resourceUrl,\n\t});\n\tconst result = await readEtaggedBatch({\n\t\tproviderId: providerId,\n\t\tdomain: resolvedDomain,\n\t\tconnectionId: connectionId,\n\t\ttargets: trimmedTargets,\n\t\tsupportsEtags: integration.supportsIssueEtagsByResourceId,\n\t\treadFull: batch =>\n\t\t\trunCaptured(\n\t\t\t\tproviderId,\n\t\t\t\tresolvedDomain,\n\t\t\t\tconnectionId,\n\t\t\t\t() => integration.getIssuesByResourceIdBatchResult(batch.map(toResourceTarget), connectionId),\n\t\t\t\t// The read core answers `undefined` when it cannot resolve a session. That must surface as a connection\n\t\t\t\t// warning, including on the primary path, rather than as nothing — or worse, be read as absence.\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\treadEtagFields: batch =>\n\t\t\trunCaptured(\n\t\t\t\tproviderId,\n\t\t\t\tresolvedDomain,\n\t\t\t\tconnectionId,\n\t\t\t\t() =>\n\t\t\t\t\tintegration.getIssuesEtagFieldsByResourceIdBatchResult(\n\t\t\t\t\t\tbatch.map(toResourceTarget),\n\t\t\t\t\t\t{ etagIncludes: etagIncludes },\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t),\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\titemEtag: issue => issueEtag(issueEtagFieldsFromShape(issue), etagIncludes),\n\t\tfieldsEtag: fields => issueEtag(fields, etagIncludes),\n\t\tunsupportedWarning: unsupported,\n\t});\n\n\treturn toIssueBatchResult(result);\n}\n\n/** The etag flow's rows as this read's results, under the `issue` name the result type gives the item. */\nfunction toIssueBatchResult(result: ProviderResult<EtaggedBatchRow<IssueShape>>): ProviderResult<IssueBatchResult> {\n\treturn {\n\t\titems: result.items.map(row => ({\n\t\t\tkey: row.key,\n\t\t\t...(row.value != null ? { issue: row.value } : {}),\n\t\t\t...(row.unchanged ? { unchanged: true as const } : {}),\n\t\t\t...(row.etag != null ? { etag: row.etag } : {}),\n\t\t})),\n\t\twarnings: result.warnings,\n\t\tfetchFailed: result.fetchFailed,\n\t};\n}\n\nfunction isCoordinateTarget(target: IssueBatchTarget): target is CoordinateTarget {\n\treturn 'owner' in target;\n}\n\nfunction isTrackerTarget(target: IssueBatchTarget): target is TrackerTarget {\n\treturn 'resourceId' in target;\n}\n\n/**\n * Trims `owner`, `repo` and `project` once, shared by every coordinate-form batch read (this one, the pull request\n * batch and the pull-requests-by-branch read): leading/trailing whitespace can't change which repository a\n * coordinate names, so it's stripped and accepted rather than refused, matching #5821's scope-name precedent.\n */\nexport function trimCoordinateFields<T extends { owner: string; repo: string; project?: string }>(target: T): T {\n\treturn { ...target, owner: target.owner.trim(), repo: target.repo.trim(), project: target.project?.trim() };\n}\n\n/** Trims `resourceId`, `resourceUrl` and `identifier` once; the validated value must be the one sent to the provider. */\nfunction trimTrackerTarget(target: TrackerTarget): TrackerTarget {\n\treturn {\n\t\t...target,\n\t\tresourceId: target.resourceId.trim(),\n\t\tresourceUrl: target.resourceUrl?.trim() || undefined,\n\t\tidentifier: target.identifier.trim(),\n\t};\n}\n\nfunction findInvalidCoordinateTarget(\n\tproviderId: IntegrationIds,\n\ttargets: readonly CoordinateTarget[],\n): string | undefined {\n\tconst isAzure = isAzureProviderId(providerId);\n\tconst isGitHub = isGitHubProviderId(providerId);\n\tfor (const target of targets) {\n\t\tif (!Number.isSafeInteger(target.number) || target.number <= 0) {\n\t\t\treturn `Issue batch target '${target.key}' has number ${target.number}; expected a positive integer.`;\n\t\t}\n\t\tif (isGitHub && target.number > githubGraphQLInt32Max) {\n\t\t\treturn `Issue batch target '${target.key}' has number ${target.number}; GitHub numbers are 32-bit and cannot exceed ${githubGraphQLInt32Max}.`;\n\t\t}\n\t\tif (target.owner.length === 0) {\n\t\t\treturn `Issue batch target '${target.key}' requires a non-empty owner.`;\n\t\t}\n\t\tif (isAzure) {\n\t\t\tif (!target.project) {\n\t\t\t\treturn `Issue batch target '${target.key}' requires a project for '${providerId}'.`;\n\t\t\t}\n\t\t} else if (target.repo.length === 0) {\n\t\t\treturn `Issue batch target '${target.key}' requires a non-empty repo.`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction findInvalidTrackerTarget(\n\tproviderId: IssuesHostIntegrationIds,\n\ttargets: readonly TrackerTarget[],\n): string | undefined {\n\tfor (const target of targets) {\n\t\tif (target.resourceId.length === 0) {\n\t\t\treturn `Issue batch target '${target.key}' requires a resource id.`;\n\t\t}\n\t\tif (target.identifier.length === 0) {\n\t\t\treturn `Issue batch target '${target.key}' requires an issue identifier.`;\n\t\t}\n\t\tif (providerId === IssuesCloudHostIntegrationId.Jira && !target.resourceUrl) {\n\t\t\treturn `Issue batch target '${target.key}' requires the Jira resource URL so the result contains a browser link without resource discovery.`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * The answer when no integration resolves for a batch read. Targets were asked about, so this is never a silent\n * empty success: a supplied connection or domain that no longer resolves gets its own warning, and the untargeted\n * primary path (e.g. a self-managed provider with no configured host) a connection warning, as `getCurrentAccount`\n * gives — either way `fetchFailed`, so no caller mistakes the dropped targets for an answer.\n */\nexport function unresolvedIntegration<T>(\n\tctx: ProviderReadContext,\n\tproviderId: IntegrationIds,\n\tconnectionId: string | undefined,\n\tdomain: string | undefined,\n): ProviderResult<T> {\n\tconst early = ctx.earlyReturnConnectionWarnings(providerId, connectionId, domain);\n\tif (early.warnings.length > 0) return { items: [], warnings: early.warnings, fetchFailed: true };\n\n\tconst resolvedDomain = ctx.resolveDomainForRead(providerId, connectionId, domain);\n\treturn { items: [], warnings: [noConnectionWarning(providerId, resolvedDomain, connectionId)], fetchFailed: true };\n}\n\nexport function findDuplicateKey(targets: readonly { key: string }[]): string | undefined {\n\tconst seen = new Set<string>();\n\tfor (const target of targets) {\n\t\tif (seen.has(target.key)) return target.key;\n\n\t\tseen.add(target.key);\n\t}\n\treturn undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"issueBatch.js","names":[],"sources":["../../../../../plus/integrations/src/reads/issueBatch.ts"],"sourcesContent":["import type { IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IntegrationIds, IssuesHostIntegrationIds } from '../constants.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport type { IssueEtagInclude } from '../models/integration.js';\nimport { issueEtagIncludes } from '../models/integration.js';\nimport { isIssuesIntegration } from '../models/issuesIntegration.js';\nimport { githubGraphQLInt32Max, isAzureProviderId, isGitHubProviderId } from '../providers/providerErrors.js';\nimport type { ProviderResult, ProviderWarning } from '../results.js';\nimport { areDomainsOnSameHost, hostFromDomain } from '../utils/domain.utils.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\tisIssuesSelfManagedHostIntegrationId,\n} from '../utils/integration.utils.js';\nimport type { EtaggedBatchRow } from './batchEtags.js';\nimport { readEtaggedBatch } from './batchEtags.js';\nimport type { ProviderReadContext } from './context.js';\nimport { runCaptured } from './drains.js';\nimport {\n\tfindInvalidIssueEtagInclude,\n\tissueEtag,\n\tissueEtagFieldsFromShape,\n\tnormalizeIssueEtagIncludes,\n} from './etag.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tissuesUnsupportedWarning,\n\tissueTrackerOnlySurfaceWarning,\n\tnoConnectionWarning,\n\totherWarning,\n\tunsupportedWarning,\n} from './warnings.js';\n\n/**\n * The BATCH issue read: resolve N issues BY IDENTITY in one call — `(owner, repo, number)` coordinates on a git\n * host, `(resourceId, ABC-123)` identifiers on an issue tracker.\n *\n * A sibling of the issue searches rather than a mode of one, because it answers a different question. A search\n * asks \"what matches\"; this asks \"does this exact issue exist\", which is what a caller correlating a branch name\n * to the issue it references is actually asking. Three consequences follow, and they are the reason this exists:\n *\n * - It resolves by EXACT IDENTIFIER, so relevance never enters into it.\n * - No result ceiling applies, so there is no partial window to reason about and no omission to report.\n * - An absent slot is a PROVEN ABSENCE, not \"not found within a page budget\". That is the property that matters\n * most: a caller can CACHE a miss. Emulating this with a paged list cannot prove absence without walking the\n * whole scope, so a miss stays unproven and the walk repeats on every pass, forever.\n *\n * Modeled on `countIssues` for its shape — caller-owned `key` echoed back, per-target isolation, as few requests\n * as the provider allows — because both take a set of independent questions and answer them together.\n *\n * ONE integration call per invocation when no target carries an `etag`, whatever the target count, as in\n * `getPullRequestsBatch`: the integration fans its targets out (chunked for GitHub/GHE, one request per target with\n * bounded concurrency on GitLab and Azure DevOps and on the trackers) and settles each independently, so failing\n * targets spend at most one strike of the connection's failure budget.\n *\n * With etags, up to THREE on a host with a cheap check (GitHub/GHE, GitLab and Azure DevOps; Jira Cloud and Linear\n * among the trackers): the cheap check of the targets that carry one, a full read of the rest started alongside it,\n * and a full read of the targets whose etag no longer matches — see `readEtaggedBatch`. Every other tracker still makes\n * one call and etags its rows.\n */\n\n/** One issue to resolve, echoed back under the caller's own `key`. Which form a call takes depends on its provider. */\nexport type IssueBatchTarget =\n\t/**\n\t * A git host's issue, by repository coordinate. GitHub/GHE, GitLab (and self-managed) and Azure DevOps (and\n\t * Server); Bitbucket and Bitbucket Data Center have no issues and refuse.\n\t */\n\t| {\n\t\t\tkey: string;\n\t\t\t/** GitHub owner, GitLab namespace path (may be nested), or Azure DevOps organization (or collection). */\n\t\t\towner: string;\n\t\t\t/**\n\t\t\t * Repository name. Required, but ignored on Azure DevOps, whose work items belong to the project rather\n\t\t\t * than to a repository; there it only names the project of a project-less Azure DevOps Server path read\n\t\t\t * behind a virtual directory (`{vdir}/{collection}/_git/{repo}` as owner `{vdir}`, project `{collection}`).\n\t\t\t */\n\t\t\trepo: string;\n\t\t\t/** Issue number, GitLab issue iid, or Azure DevOps work item id. */\n\t\t\tnumber: number;\n\t\t\t/** Azure DevOps project. Required there, ignored elsewhere. */\n\t\t\tproject?: string;\n\t\t\t/** The {@link IssueBatchResult.etag} of the copy the caller holds; see {@link IssueBatchResult}. */\n\t\t\tetag?: string;\n\t }\n\t/**\n\t * An issue tracker's issue, by its own identifier (e.g. `ABC-123`) within a resource. Jira (Cloud and Data\n\t * Center) and Linear. For Jira Data Center `resourceId` is the host — the instance's single resource, as\n\t * `listOrgs` reports it — and `resourceUrl` is not needed, since the browser link is built from the\n\t * connection's own base URL; Jira Cloud requires it.\n\t */\n\t| {\n\t\t\tkey: string;\n\t\t\tresourceId: string;\n\t\t\tresourceUrl?: string;\n\t\t\tidentifier: string;\n\t\t\t/**\n\t\t\t * The {@link IssueBatchResult.etag} of the copy the caller holds. Checked cheaply on Jira Cloud and Linear;\n\t\t\t * every other tracker accepts it but reads the issue in full.\n\t\t\t */\n\t\t\tetag?: string;\n\t };\n\ntype CoordinateTarget = Extract<IssueBatchTarget, { owner: string }>;\ntype TrackerTarget = Extract<IssueBatchTarget, { resourceId: string }>;\n\n/**\n * The answer for one {@link IssueBatchTarget}, in one of four states:\n * - `{ key, issue, etag }` — read in full. Every fully read row carries an `etag`, whether or not the caller sent\n * one, so a caller can seed its etags from the reads it already makes.\n * - `{ key, unchanged: true, etag }` — the cheap check proved the caller's copy current, and nothing else was\n * fetched. Only for a target that sent an `etag`, on a host with a cheap check.\n * - `{ key }` — PROVEN ABSENT.\n * - no row, with `fetchFailed` and a warning — the read could not check. Never treat that as absent.\n */\nexport interface IssueBatchResult {\n\tkey: string;\n\t/**\n\t * The resolved issue, or `undefined` when it PROVABLY does not exist (or is not visible to this connection) —\n\t * or when it is `unchanged`.\n\t *\n\t * Absent is an answer here, unlike every paged read on this facade: a target that FAILED — its own request\n\t * outright, or just its own alias within an otherwise-answering GitHub chunk (e.g. an org enforcing SAML SSO\n\t * the token isn't authorized for) — is not returned at all and sets `fetchFailed`, so a caller can tell\n\t * \"proven absent\" from \"unknown\" and cache the first without ever caching the second.\n\t */\n\tissue?: IssueShape;\n\t/**\n\t * Opaque: compare for equality only, never parse. Computed by core from the issue's change state — its state and\n\t * update time, plus each input the call's `etagIncludes` listed — and NOT the provider's HTTP ETag. Send it back\n\t * as the target's `etag`. An etag from another scheme or another `etagIncludes` set simply compares unequal and\n\t * costs a full read, never a false `unchanged`. A host can add a reaction without moving the update time, so\n\t * unless `'reactions'` is listed, an `unchanged` copy's `thumbsUpCount` may be stale.\n\t */\n\tetag?: string;\n\t/** The caller's copy is current (its `etag` matched); `issue` is absent because nothing was read. */\n\tunchanged?: true;\n}\n\nconst surface = 'Batch issue resolution';\n\nfunction refused(warning: ProviderWarning): ProviderResult<IssueBatchResult> {\n\treturn { items: [], warnings: [warning], fetchFailed: true };\n}\n\nexport async function getIssuesBatch(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\ttargets: readonly IssueBatchTarget[];\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain. Used only when the requested connection has no configured domain;\n\t\t * it must come from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t\t/**\n\t\t * Widens every etag to the listed inputs, each of which a host changes without moving the issue's update\n\t\t * time, and each of which costs its own fields in the cheap check. `'reactions'` is the thumbs-up count; it\n\t\t * widens nothing on a host whose rows carry no real count (Azure DevOps work items and the trackers). Order\n\t\t * and repeats don't matter. An unknown value refuses the whole call. Changes only which etag is computed; the\n\t\t * full read is the same. Calls that differ in this set never match each other's etags.\n\t\t */\n\t\tetagIncludes?: readonly IssueEtagInclude[];\n\t},\n): Promise<ProviderResult<IssueBatchResult>> {\n\t// Nothing was asked for, so nothing is missing: an empty success, not a refusal.\n\tif (options.targets.length === 0) return { items: [], warnings: [] };\n\n\tconst duplicateKey = findDuplicateKey(options.targets);\n\tif (duplicateKey != null) {\n\t\t// Refuses the whole call rather than deduping: `key` exists so the caller can match results without\n\t\t// positional bookkeeping, and two results under one key make that ambiguous for EVERY target, not just\n\t\t// the repeated one.\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Duplicate issue batch target key '${duplicateKey}'; keys identify results, so each must be unique.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// Same as a bad target: refused whole before any request, naming the value and what's allowed.\n\tconst invalidInclude = findInvalidIssueEtagInclude(options.etagIncludes ?? []);\n\tif (invalidInclude != null) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`Unknown issue etag include '${invalidInclude}'; expected one of ${issueEtagIncludes.map(i => `'${i}'`).join(', ')}.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst etagIncludes = normalizeIssueEtagIncludes(options.etagIncludes ?? []);\n\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn getIssuesBatchForTracker(\n\t\t\tctx,\n\t\t\toptions.providerId,\n\t\t\toptions.targets,\n\t\t\toptions.connectionId,\n\t\t\toptions.domain,\n\t\t\tetagIncludes,\n\t\t);\n\t}\n\n\tconst targets = options.targets;\n\tif (!targets.every(isCoordinateTarget)) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tundefined,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} for '${options.providerId}' takes repository coordinates { key, owner, repo, number, project? }, not tracker identifiers.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// The validated value must be the one sent to the provider, so every string field is trimmed once here.\n\tconst trimmedTargets = targets.map(trimCoordinateFields);\n\n\t// Deterministic caller bugs, refused like a duplicate key rather than sent upstream, where a blank owner or repo\n\t// would come back as a missing repository — a proven absence the caller would cache.\n\tconst invalid = findInvalidCoordinateTarget(options.providerId, trimmedTargets);\n\tif (invalid != null) {\n\t\treturn refused(otherWarning(options.providerId, undefined, options.connectionId, invalid));\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\treturn unresolvedIntegration(ctx, options.providerId, options.connectionId, options.domain);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refused(gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, surface));\n\t}\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\n\tif (!integration.supportsIssues) {\n\t\treturn refused(issuesUnsupportedWarning(options.providerId, domain, options.connectionId));\n\t}\n\n\tconst toCoordinate = (t: CoordinateTarget) => ({\n\t\towner: t.owner,\n\t\trepo: t.repo,\n\t\tnumber: t.number,\n\t\tproject: t.project,\n\t});\n\tconst result = await readEtaggedBatch({\n\t\tproviderId: options.providerId,\n\t\tdomain: domain,\n\t\tconnectionId: options.connectionId,\n\t\ttargets: trimmedTargets,\n\t\tsupportsEtags: integration.supportsIssueEtags,\n\t\treadFull: batch =>\n\t\t\trunCaptured(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t() => integration.getIssuesBatchResult(batch.map(toCoordinate), undefined, options.connectionId),\n\t\t\t\t// A missing session must surface as a connection warning, including on the primary path, which\n\t\t\t\t// otherwise reads it as \"not connected\" and says nothing — and this read would then call it\n\t\t\t\t// unsupported.\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\treadEtagFields: batch =>\n\t\t\trunCaptured(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t() =>\n\t\t\t\t\tintegration.getIssuesEtagFieldsResult(\n\t\t\t\t\t\tbatch.map(toCoordinate),\n\t\t\t\t\t\t{ etagIncludes: etagIncludes },\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t),\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\titemEtag: issue => issueEtag(issueEtagFieldsFromShape(issue), etagIncludes),\n\t\tfieldsEtag: fields => issueEtag(fields, etagIncludes),\n\t\t// A provider that doesn't implement the batch hook answers `undefined` with no error. Either way its targets\n\t\t// are DROPPED rather than reported as absent — the difference between \"unknown\" and \"proven absent\" is the\n\t\t// read's whole value, and a failure must never be cached as an answer.\n\t\tunsupportedWarning: () =>\n\t\t\tunsupportedWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t`${surface} is not supported by '${options.providerId}'; resolve issues individually instead.`,\n\t\t\t),\n\t});\n\n\treturn toIssueBatchResult(result);\n}\n\n/**\n * The tracker form. ONE integration call per invocation when no target carries an `etag`, whatever the target count:\n * the integration makes one single-issue request per target, with bounded concurrency, and settles each independently\n * — see `IssuesIntegration.getIssuesByResourceIdBatchResult`. With etags, a tracker with a cheap check (Jira Cloud's\n * bulk fetch, Linear's per-team query) takes the coordinate form's `readEtaggedBatch` flow; every other tracker\n * ignores a target's `etag` and reads and etags every found row in full.\n *\n * `resourceId` is trusted and the read does no resource discovery. It is handed to the provider's by-resource-id\n * read as-is, never wrapped into a synthesized descriptor: Linear and Trello answer `undefined` for a descriptor\n * that fails `isIssueResourceDescriptor`, which would be published as a proven absence.\n *\n * `domain` selects the host of a self-managed tracker (Jira Data Center), as it does for\n * `listIssueTrackerIssuesPage`, and is ignored for the cloud trackers, which have a single canonical host. Unlike\n * the paged reads, this one never falls back to the primary connection for a self-managed tracker: it requires a\n * `domain` or a `connectionId` with a configured host and refuses otherwise. Two self-hosted instances routinely\n * issue the same project and issue keys, and `issue: undefined` is a proven absence a caller may cache — an answer\n * from whichever host happens to be primary would be cached under a key that names a different instance (#5872).\n */\nasync function getIssuesBatchForTracker(\n\tctx: ProviderReadContext,\n\tproviderId: IssuesHostIntegrationIds,\n\ttargets: readonly IssueBatchTarget[],\n\tconnectionId: string | undefined,\n\tdomain: string | undefined,\n\tetagIncludes: readonly IssueEtagInclude[],\n): Promise<ProviderResult<IssueBatchResult>> {\n\tif (!targets.every(isTrackerTarget)) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\tproviderId,\n\t\t\t\tundefined,\n\t\t\t\tconnectionId,\n\t\t\t\t`${surface} for '${providerId}' takes tracker identifiers { key, resourceId, resourceUrl?, identifier }, not repository coordinates.`,\n\t\t\t),\n\t\t);\n\t}\n\n\t// The validated value must be the one sent to the provider, so every string field is trimmed once here.\n\tconst trimmedTargets = targets.map(trimTrackerTarget);\n\n\t// Deterministic caller bugs, refused like a duplicate key rather than sent upstream to fail one by one.\n\tconst invalid = findInvalidTrackerTarget(providerId, trimmedTargets);\n\tif (invalid != null) {\n\t\treturn refused(otherWarning(providerId, undefined, connectionId, invalid));\n\t}\n\n\t// A `domain` only selects a host when it parses to one, and a `connectionId` only when it names a configured\n\t// connection that has one; anything else would resolve the primary host instead, which is the fallback this\n\t// read refuses.\n\tif (\n\t\tisIssuesSelfManagedHostIntegrationId(providerId) &&\n\t\t(domain != null\n\t\t\t? hostFromDomain(domain) == null\n\t\t\t: connectionId == null ||\n\t\t\t\t!ctx.getConfigured(providerId).some(c => c.id === connectionId && hostFromDomain(c.domain) != null))\n\t) {\n\t\treturn refused(\n\t\t\totherWarning(\n\t\t\t\tproviderId,\n\t\t\t\tundefined,\n\t\t\t\tconnectionId,\n\t\t\t\t`${surface} requires a domain or a configured connection id for '${providerId}': every configured host can hold a different issue under the same key, so the read does not answer from the primary connection.`,\n\t\t\t),\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(providerId, connectionId, domain);\n\tif (integration == null) return unresolvedIntegration(ctx, providerId, connectionId, domain);\n\tif (!isIssuesIntegration(integration)) {\n\t\treturn refused(issueTrackerOnlySurfaceWarning(providerId, connectionId, surface));\n\t}\n\n\tconst unsupported = (): ProviderWarning =>\n\t\tunsupportedWarning(\n\t\t\tproviderId,\n\t\t\tundefined,\n\t\t\tconnectionId,\n\t\t\t`${surface} is not supported by '${providerId}'; its single-issue read cannot prove an absence, so a miss would not be safe to cache.`,\n\t\t);\n\tif (!integration.supportsIssueLookupByResourceId) return refused(unsupported());\n\n\t// A self-managed tracker's single resource is its host, and a caller keys (and caches) each answer by\n\t// `resourceId` while the read is addressed to the host `domain`/`connectionId` resolved. When they disagree,\n\t// host B's answer — including a cacheable absence — would be stored under host A's key. Refused whole, like\n\t// the caller bugs above: a target naming another host means the call selected a host its caller did not mean.\n\tif (isIssuesSelfManagedHostIntegrationId(providerId)) {\n\t\tconst mismatched = trimmedTargets.find(t => !areDomainsOnSameHost(t.resourceId, integration.domain));\n\t\tif (mismatched != null) {\n\t\t\treturn refused(\n\t\t\t\totherWarning(\n\t\t\t\t\tproviderId,\n\t\t\t\t\tundefined,\n\t\t\t\t\tconnectionId,\n\t\t\t\t\t`Issue batch target '${mismatched.key}' requires the resource id of '${providerId}' to name the host the read resolved to.`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\n\tconst resolvedDomain = ctx.domainForRead(integration, providerId, connectionId, domain);\n\n\tconst toResourceTarget = (t: TrackerTarget) => ({\n\t\tresourceId: t.resourceId,\n\t\tidentifier: t.identifier,\n\t\tresourceUrl: t.resourceUrl,\n\t});\n\tconst result = await readEtaggedBatch({\n\t\tproviderId: providerId,\n\t\tdomain: resolvedDomain,\n\t\tconnectionId: connectionId,\n\t\ttargets: trimmedTargets,\n\t\tsupportsEtags: integration.supportsIssueEtagsByResourceId,\n\t\treadFull: batch =>\n\t\t\trunCaptured(\n\t\t\t\tproviderId,\n\t\t\t\tresolvedDomain,\n\t\t\t\tconnectionId,\n\t\t\t\t() => integration.getIssuesByResourceIdBatchResult(batch.map(toResourceTarget), connectionId),\n\t\t\t\t// The read core answers `undefined` when it cannot resolve a session. That must surface as a connection\n\t\t\t\t// warning, including on the primary path, rather than as nothing — or worse, be read as absence.\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\treadEtagFields: batch =>\n\t\t\trunCaptured(\n\t\t\t\tproviderId,\n\t\t\t\tresolvedDomain,\n\t\t\t\tconnectionId,\n\t\t\t\t() =>\n\t\t\t\t\tintegration.getIssuesEtagFieldsByResourceIdBatchResult(\n\t\t\t\t\t\tbatch.map(toResourceTarget),\n\t\t\t\t\t\t{ etagIncludes: etagIncludes },\n\t\t\t\t\t\tconnectionId,\n\t\t\t\t\t),\n\t\t\t\t{ warnOnMissingSession: true },\n\t\t\t),\n\t\titemEtag: issue => issueEtag(issueEtagFieldsFromShape(issue), etagIncludes),\n\t\tfieldsEtag: fields => issueEtag(fields, etagIncludes),\n\t\tunsupportedWarning: unsupported,\n\t});\n\n\treturn toIssueBatchResult(result);\n}\n\n/** The etag flow's rows as this read's results, under the `issue` name the result type gives the item. */\nfunction toIssueBatchResult(result: ProviderResult<EtaggedBatchRow<IssueShape>>): ProviderResult<IssueBatchResult> {\n\treturn {\n\t\titems: result.items.map(row => ({\n\t\t\tkey: row.key,\n\t\t\t...(row.value != null ? { issue: row.value } : {}),\n\t\t\t...(row.unchanged ? { unchanged: true as const } : {}),\n\t\t\t...(row.etag != null ? { etag: row.etag } : {}),\n\t\t})),\n\t\twarnings: result.warnings,\n\t\tfetchFailed: result.fetchFailed,\n\t};\n}\n\nfunction isCoordinateTarget(target: IssueBatchTarget): target is CoordinateTarget {\n\treturn 'owner' in target;\n}\n\nfunction isTrackerTarget(target: IssueBatchTarget): target is TrackerTarget {\n\treturn 'resourceId' in target;\n}\n\n/**\n * Trims `owner`, `repo` and `project` once, shared by every coordinate-form batch read (this one, the pull request\n * batch and the pull-requests-by-branch read): leading/trailing whitespace can't change which repository a\n * coordinate names, so it's stripped and accepted rather than refused, matching #5821's scope-name precedent.\n */\nexport function trimCoordinateFields<T extends { owner: string; repo: string; project?: string }>(target: T): T {\n\treturn { ...target, owner: target.owner.trim(), repo: target.repo.trim(), project: target.project?.trim() };\n}\n\n/** Trims `resourceId`, `resourceUrl` and `identifier` once; the validated value must be the one sent to the provider. */\nfunction trimTrackerTarget(target: TrackerTarget): TrackerTarget {\n\treturn {\n\t\t...target,\n\t\tresourceId: target.resourceId.trim(),\n\t\tresourceUrl: target.resourceUrl?.trim() || undefined,\n\t\tidentifier: target.identifier.trim(),\n\t};\n}\n\nfunction findInvalidCoordinateTarget(\n\tproviderId: IntegrationIds,\n\ttargets: readonly CoordinateTarget[],\n): string | undefined {\n\tconst isAzure = isAzureProviderId(providerId);\n\tconst isGitHub = isGitHubProviderId(providerId);\n\tfor (const target of targets) {\n\t\tif (!Number.isSafeInteger(target.number) || target.number <= 0) {\n\t\t\treturn `Issue batch target '${target.key}' has number ${target.number}; expected a positive integer.`;\n\t\t}\n\t\tif (isGitHub && target.number > githubGraphQLInt32Max) {\n\t\t\treturn `Issue batch target '${target.key}' has number ${target.number}; GitHub numbers are 32-bit and cannot exceed ${githubGraphQLInt32Max}.`;\n\t\t}\n\t\tif (target.owner.length === 0) {\n\t\t\treturn `Issue batch target '${target.key}' requires a non-empty owner.`;\n\t\t}\n\t\tif (isAzure) {\n\t\t\tif (!target.project) {\n\t\t\t\treturn `Issue batch target '${target.key}' requires a project for '${providerId}'.`;\n\t\t\t}\n\t\t} else if (target.repo.length === 0) {\n\t\t\treturn `Issue batch target '${target.key}' requires a non-empty repo.`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction findInvalidTrackerTarget(\n\tproviderId: IssuesHostIntegrationIds,\n\ttargets: readonly TrackerTarget[],\n): string | undefined {\n\tfor (const target of targets) {\n\t\tif (target.resourceId.length === 0) {\n\t\t\treturn `Issue batch target '${target.key}' requires a resource id.`;\n\t\t}\n\t\tif (target.identifier.length === 0) {\n\t\t\treturn `Issue batch target '${target.key}' requires an issue identifier.`;\n\t\t}\n\t\tif (providerId === IssuesCloudHostIntegrationId.Jira && !target.resourceUrl) {\n\t\t\treturn `Issue batch target '${target.key}' requires the Jira resource URL so the result contains a browser link without resource discovery.`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * The answer when no integration resolves for a batch read. Targets were asked about, so this is never a silent\n * empty success: a supplied connection or domain that no longer resolves gets its own warning, and the untargeted\n * primary path (e.g. a self-managed provider with no configured host) a connection warning, as `getCurrentAccount`\n * gives — either way `fetchFailed`, so no caller mistakes the dropped targets for an answer.\n */\nexport function unresolvedIntegration<T>(\n\tctx: ProviderReadContext,\n\tproviderId: IntegrationIds,\n\tconnectionId: string | undefined,\n\tdomain: string | undefined,\n): ProviderResult<T> {\n\tconst early = ctx.earlyReturnConnectionWarnings(providerId, connectionId, domain);\n\tif (early.warnings.length > 0) return { items: [], warnings: early.warnings, fetchFailed: true };\n\n\tconst resolvedDomain = ctx.resolveDomainForRead(providerId, connectionId, domain);\n\treturn { items: [], warnings: [noConnectionWarning(providerId, resolvedDomain, connectionId)], fetchFailed: true };\n}\n\nexport function findDuplicateKey(targets: readonly { key: string }[]): string | undefined {\n\tconst seen = new Set<string>();\n\tfor (const target of targets) {\n\t\tif (seen.has(target.key)) return target.key;\n\n\t\tseen.add(target.key);\n\t}\n\treturn undefined;\n}\n"],"mappings":"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"}
|
|
@@ -10,7 +10,10 @@ export declare function listIssueTrackerIssuesPage(ctx: ProviderReadContext, opt
|
|
|
10
10
|
org?: string;
|
|
11
11
|
project?: string;
|
|
12
12
|
filters?: IssueFilter[];
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Broadens the read to every assignee. Scopes to the user's issues when omitted: those assigned to them, and on
|
|
15
|
+
* Linear also the GitHub-synced issues they created (unless `filters` asks for `Assignee` by name).
|
|
16
|
+
*/
|
|
14
17
|
includeAllAssignees?: boolean;
|
|
15
18
|
/**
|
|
16
19
|
* How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the
|
|
@@ -4,9 +4,9 @@ import "../constants.js";
|
|
|
4
4
|
import { appendDedupedWarning, reconcileOmissionsWithFailure } from "../results.js";
|
|
5
5
|
import { assessCollectionMetadata, mergeAssessmentInto } from "../collectionMetadata.js";
|
|
6
6
|
import "../providerFilters.js";
|
|
7
|
-
import { isIssuesHostIntegrationId, isLinearIntegration } from "../utils/integration.utils.js";
|
|
8
7
|
import { mergeCollectionMetadata, parsePageCursor } from "../providers/utils/providerPaging.js";
|
|
9
8
|
import { providersMetadata } from "../providers/models.js";
|
|
9
|
+
import { isIssuesHostIntegrationId, isLinearIntegration } from "../utils/integration.utils.js";
|
|
10
10
|
import { isIssuesIntegration } from "../models/issuesIntegration.js";
|
|
11
11
|
import { linearProjectIds, parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from "./cursors.js";
|
|
12
12
|
import { incompleteReadWarning, issueTrackerOnlySurfaceWarning, otherWarning, projectCutWarning, unmatchedSelectorWarning, unmergeableIssueSortWarning, unsupportedIssueSortWarning, unsupportedWarning } from "./warnings.js";
|