@gitkraken/core-gitlens 0.9.4 → 0.9.5
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 +22 -1
- package/dist/git/models/issue.d.ts +25 -2
- package/dist/git/models/issue.js +3 -1
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/models/pullRequest.d.ts +55 -5
- package/dist/git/models/pullRequest.js +5 -1
- package/dist/git/models/pullRequest.js.map +1 -1
- package/dist/git/utils/issue.utils.d.ts +4 -0
- package/dist/git/utils/issue.utils.js +4 -0
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/git/utils/pullRequest.utils.d.ts +4 -0
- package/dist/git/utils/pullRequest.utils.js +8 -0
- package/dist/git/utils/pullRequest.utils.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +64 -4
- package/dist/plus/git-github/api/github.js +232 -15
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/models.d.ts +48 -6
- package/dist/plus/git-github/models.js +11 -10
- package/dist/plus/git-github/models.js.map +1 -1
- package/dist/plus/integrations/fieldPresence.d.ts +75 -0
- package/dist/plus/integrations/fieldPresence.js +381 -0
- package/dist/plus/integrations/fieldPresence.js.map +1 -0
- package/dist/plus/integrations/index.d.ts +6 -4
- package/dist/plus/integrations/index.js +2 -1
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +7 -3
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +56 -6
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +103 -9
- package/dist/plus/integrations/models/gitHostIntegration.js +116 -5
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +91 -4
- package/dist/plus/integrations/models/integration.js +56 -7
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts +44 -3
- package/dist/plus/integrations/models/issuesIntegration.js +51 -1
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.js +3 -3
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +36 -4
- package/dist/plus/integrations/providers/azure/models.js +118 -8
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +102 -5
- package/dist/plus/integrations/providers/azureDevOps.js +264 -45
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +30 -6
- package/dist/plus/integrations/providers/azureDevOpsServer.js +82 -21
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +21 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +74 -10
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/models.d.ts +40 -3
- package/dist/plus/integrations/providers/bitbucket/models.js +30 -7
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +2 -6
- package/dist/plus/integrations/providers/bitbucket-server/models.js +1 -3
- package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.d.ts +3 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +4 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +2 -2
- package/dist/plus/integrations/providers/bitbucket-server.js +7 -5
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts +22 -2
- package/dist/plus/integrations/providers/bitbucket.js +29 -6
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts +40 -3
- package/dist/plus/integrations/providers/github.js +133 -11
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +34 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.js +118 -4
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/models.d.ts +70 -5
- package/dist/plus/integrations/providers/gitlab/models.js +106 -13
- package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +31 -2
- package/dist/plus/integrations/providers/gitlab.js +48 -8
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/providers/jira-server.d.ts +1 -0
- package/dist/plus/integrations/providers/jira-server.js +9 -6
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +20 -1
- package/dist/plus/integrations/providers/jira.js +101 -6
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +79 -0
- package/dist/plus/integrations/providers/jiraIssueByKey.js +58 -7
- package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
- package/dist/plus/integrations/providers/linear.d.ts +19 -1
- package/dist/plus/integrations/providers/linear.js +97 -5
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +42 -0
- package/dist/plus/integrations/providers/linearIssuesEtag.js +94 -0
- package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -0
- package/dist/plus/integrations/providers/models.d.ts +51 -7
- package/dist/plus/integrations/providers/models.js +45 -16
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.js +8 -1
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +62 -3
- package/dist/plus/integrations/providers/providersApi.js +145 -4
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/pullRequestReviews.d.ts +13 -0
- package/dist/plus/integrations/providers/pullRequestReviews.js +30 -6
- package/dist/plus/integrations/providers/pullRequestReviews.js.map +1 -1
- package/dist/plus/integrations/providers/trello.js +10 -7
- package/dist/plus/integrations/providers/trello.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts +11 -0
- package/dist/plus/integrations/providers/utils/providerPaging.js +47 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/providers/utils.d.ts +1 -1
- package/dist/plus/integrations/reads/batchEtags.d.ts +69 -0
- package/dist/plus/integrations/reads/batchEtags.js +114 -0
- package/dist/plus/integrations/reads/batchEtags.js.map +1 -0
- package/dist/plus/integrations/reads/counts.js +2 -2
- package/dist/plus/integrations/reads/counts.js.map +1 -1
- package/dist/plus/integrations/reads/currentAccount.js +2 -2
- package/dist/plus/integrations/reads/currentAccount.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +13 -3
- package/dist/plus/integrations/reads/drains.js +29 -5
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/etag.d.ts +26 -0
- package/dist/plus/integrations/reads/etag.js +108 -0
- package/dist/plus/integrations/reads/etag.js.map +1 -0
- package/dist/plus/integrations/reads/hierarchy.js +3 -3
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +47 -16
- package/dist/plus/integrations/reads/issueBatch.js +55 -87
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +4 -4
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +1 -1
- package/dist/plus/integrations/reads/issues.js +4 -4
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.d.ts +44 -6
- package/dist/plus/integrations/reads/pullRequestBatch.js +31 -34
- package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBranches.d.ts +5 -2
- package/dist/plus/integrations/reads/pullRequestBranches.js +4 -3
- package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.js +8 -4
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/sweeps.js +8 -4
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +23 -8
- package/dist/plus/integrations/reads/warnings.js +43 -19
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +12 -4
- package/dist/plus/integrations/results.js +2 -0
- package/dist/plus/integrations/results.js.map +1 -1
- package/docs/integrations.md +385 -23
- package/docs/kepler-read-api-parity.md +16 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pullRequests.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequests.ts"],"sourcesContent":["import type { PullRequestShape, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderPullRequest, ProviderReposInput, PullRequestFilter } from '../providers/models.js';\nimport { fromProviderPullRequest, PagingMode, providersMetadata } from '../providers/models.js';\nimport type { ProviderPagedResult } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getCurrentAccountIdentity, runCaptured } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\ttruncationWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\nexport async function listPullRequestsPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\trepos?: ProviderReposInput;\n\t\tstates?: PullRequestStateFilter[];\n\t\t/**\n\t\t * PR relationship filters (e.g. `[Author, Assignee, ReviewRequested]`). On repo-scoped reads members\n\t\t * combine as provider query constraints (normally an intersection); on account-wide reads they form an\n\t\t * exact OR union. The whole set is validated against the selected path's capability.\n\t\t */\n\t\tfilters?: PullRequestFilter[];\n\t\t/**\n\t\t * Account-wide only: include review-requested PRs when the provider's native \"my PRs\" query omits them.\n\t\t * This extends the provider-native result; it is not a narrowing filter.\n\t\t */\n\t\tincludeReviewRequested?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\t/**\n\t\t * Requests the lightweight row shape (see {@link IntegrationManager.listPullRequestsPage}). Opt-in\n\t\t * only, and only on the repo-scoped branch: the account-wide branch has always read the lightweight\n\t\t * shape and keeps doing so, so an explicit `false` does not widen it back to the full projection.\n\t\t */\n\t\tsummary?: boolean;\n\t\titemsPerPage?: number;\n\t\tforceSync?: boolean;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain when no configured connection supplies one. The value must come\n\t\t * from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<PullRequestShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\t// With no repos this is an account-wide \"my PRs\" read; the repo-scoped core rejects an empty `repos`\n\t// input, so route to the account-wide, inherently user-scoped core instead (see drainPullRequests).\n\t// That provider-defined path takes no `pageSize`. A page-only request is handled by walking any opaque\n\t// continuations it supplies below. Do NOT synthesize a page-number cursor for it: cursor-based queries\n\t// (e.g. GitHub `involves:`) ignore a page number and would return their first page.\n\tconst accountWide = (options.repos?.length ?? 0) === 0;\n\tconst cursor = accountWide ? options.cursor : (options.cursor ?? pageToCursor(page));\n\n\tconst resolvedFilters = accountWide\n\t\t? resolveAccountWidePullRequestFilters(options.providerId, options.filters)\n\t\t: resolvePullRequestFilters(options.providerId, options.filters);\n\tif (resolvedFilters.unsupported) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\taccountWide\n\t\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\toptions.filters ?? [],\n\t\t\t\t\t\t)\n\t\t\t\t\t: unsupportedFiltersWarning(options.providerId, domain, options.connectionId),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// Everything but the cursor, shared by the initial read and the drain's re-reads below so the two cannot\n\t// drift. `summary` is the reason this matters beyond tidiness: a re-read that dropped it would both\n\t// revert to the full payload — on the very path that issues the most requests — and return rows of a\n\t// different shape than the first page, since a summary row reports `headCommit`/`commitCount` as absent.\n\t// `summary` is pinned true here rather than forwarded: this branch has never had a full projection to\n\t// fall back to (see the option's doc), so honoring an explicit `false` would promise a widening the read\n\t// cannot deliver.\n\tconst accountWideInput = {\n\t\tstate: options.states,\n\t\tincludeReviewRequested: options.includeReviewRequested ?? false,\n\t\tfilters: resolvedFilters.filters,\n\t\tsummary: true,\n\t};\n\t// `filters` scopes the read to the current user (the core resolves the account for these), so it returns\n\t// the user's PRs. `pageSize` rides along so PagingMode.Repo hosts (GitLab, Bitbucket, Azure), whose\n\t// per-repo cursor path ignores a synthesized page-number cursor, honor the requested size.\n\tconst scopedInput = {\n\t\tstate: options.states,\n\t\tfilters: resolvedFilters.filters,\n\t\tpageSize: options.itemsPerPage,\n\t\tsummary: options.summary,\n\t};\n\tconst readPage = (pageCursor: string | undefined, requestedPage?: number) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\taccountWide\n\t\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t\t{ ...accountWideInput, cursor: pageCursor },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t)\n\t\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\t\toptions.repos ?? [],\n\t\t\t\t\t\t\t{ ...scopedInput, cursor: pageCursor, page: requestedPage },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t),\n\t\t\t{\n\t\t\t\twarnOnMissingSession: warnOnMissingSessionForDomain(options.providerId, options.domain),\n\t\t\t},\n\t\t);\n\tconst { value, warning } = await readPage(cursor, options.page != null ? page : undefined);\n\n\tlet items = value?.values ?? [];\n\t// Cursor-only account-wide reads start at page 1; a page-only request is advanced through opaque\n\t// continuations below. Repo-scoped reads report the requested page unless the provider reports its own.\n\tlet paged = toProviderPageInfo(items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\n\t// Cursor-only reads ignore a synthesized page-number cursor, so a page-only request is advanced through\n\t// the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\t(accountWide || providersMetadata[options.providerId]?.pullRequestsPagingMode === PagingMode.Repos) &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage<ProviderPullRequest>(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\t// A drain advances through provider continuations, so only the cursor positions these reads.\n\t\t\t\treadPage: (cursor: string) => readPage(cursor),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t}\n\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on. The account-wide read has no\n\t// page to synthesize, and neither does a host that doesn't honor a page number — see\n\t// {@link IntegrationService.isPageNumberAdvanceable} for which do and why absence of a declared mode\n\t// does NOT count as one.\n\tconst pageAdvanceable =\n\t\t!accountWide && isPageNumberAdvanceable(providersMetadata[options.providerId]?.pullRequestsPagingMode);\n\tconst continuation = resolveContinuation(paged, pageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A single-page provider read that couldn't confirm completeness sets `paging.truncated`; surface it\n\t// as a terminal `page.truncated` (not `hasMore`, which has no cursor to advance) so the caller knows\n\t// the page may be incomplete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise. Ordinary\n\t\t// continuation is already expressed by `hasMore`/`cursor`, so reaching here means this page itself\n\t\t// couldn't be completed — raising anything would return the same page.\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Pull request',\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\tconst currentAccount = items.some(pr => pr.author != null)\n\t\t? await getCurrentAccountIdentity(integration, options.connectionId)\n\t\t: undefined;\n\treturn {\n\t\t// Normalize the raw provider-apis PRs to the GitLens-owned shape at the surface boundary.\n\t\titems: items.map(pr => fromProviderPullRequest(pr, integration, { currentAccount: currentAccount })),\n\t\twarnings: warnings,\n\t\t// The account-wide read can't take a page size, so don't echo the requested `itemsPerPage` as if it\n\t\t// had been applied — report what came back.\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page`,\n\t\t\t// and a caller-threaded cursor advanced the provider, so neither leaves a cursor-only read stuck\n\t\t\t// reporting page 1.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: pageAdvanceable,\n\t\t\t}),\n\t\t\titemsPerPage: accountWide ? items.length : paged.page.itemsPerPage,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\t// A metadata failure means items are incomplete even when the read didn't throw; a thrown error with\n\t\t// no recovered value is the pre-existing failure case.\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pullRequests.js","names":[],"sources":["../../../../../plus/integrations/src/reads/pullRequests.ts"],"sourcesContent":["import type { PullRequest, PullRequestShape, PullRequestStateFilter } from '@gitlens/git/models/pullRequest.js';\nimport { mergeAssessmentInto } from '../collectionMetadata.js';\nimport type { IntegrationIds } from '../constants.js';\nimport type { ProviderPullRequest, ProviderReposInput, PullRequestFilter } from '../providers/models.js';\nimport { PagingMode, providersMetadata, toPullRequestRow } from '../providers/models.js';\nimport type { ProviderPagedResult } from '../results.js';\nimport { reconcileOmissionsWithFailure } from '../results.js';\nimport {\n\tisGitHostIntegration,\n\tisIssuesHostIntegrationId,\n\twarnOnMissingSessionForDomain,\n} from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { getPullRequestViewers, runCaptured } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tdrainToRequestedPage,\n\tisPageNumberAdvanceable,\n\tpageToCursor,\n\trefusedPage,\n\tresolveContinuation,\n\tresolveCurrentPage,\n\ttoProviderPageInfo,\n} from './paging.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\ttruncationWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\nexport async function listPullRequestsPage(\n\tctx: ProviderReadContext,\n\toptions: {\n\t\tproviderId: IntegrationIds;\n\t\trepos?: ProviderReposInput;\n\t\tstates?: PullRequestStateFilter[];\n\t\t/**\n\t\t * PR relationship filters (e.g. `[Author, Assignee, ReviewRequested]`). On repo-scoped reads members\n\t\t * combine as provider query constraints (normally an intersection); on account-wide reads they form an\n\t\t * exact OR union. The whole set is validated against the selected path's capability.\n\t\t */\n\t\tfilters?: PullRequestFilter[];\n\t\t/**\n\t\t * Account-wide only: include review-requested PRs when the provider's native \"my PRs\" query omits them.\n\t\t * This extends the provider-native result; it is not a narrowing filter.\n\t\t */\n\t\tincludeReviewRequested?: boolean;\n\t\tpage?: number;\n\t\tcursor?: string;\n\t\t/**\n\t\t * Requests the lightweight row shape (see {@link IntegrationManager.listPullRequestsPage}). Opt-in\n\t\t * only, and only on the repo-scoped branch: the account-wide branch has always read the lightweight\n\t\t * shape and keeps doing so, so an explicit `false` does not widen it back to the full projection.\n\t\t */\n\t\tsummary?: boolean;\n\t\titemsPerPage?: number;\n\t\tforceSync?: boolean;\n\t\tconnectionId?: string;\n\t\t/**\n\t\t * Explicit self-managed host domain when no configured connection supplies one. The value must come\n\t\t * from the trusted authentication configuration, not repository or remote data.\n\t\t */\n\t\tdomain?: string;\n\t},\n): Promise<ProviderPagedResult<PullRequestShape>> {\n\t// Truncated as well as floored, like every other paged read here: `page` is reported back as\n\t// `page.currentPage` and compared against the position a drain reached, and neither can be fractional.\n\tconst page = Math.max(1, Math.trunc(options.page ?? 1));\n\tif (isIssuesHostIntegrationId(options.providerId)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(options.providerId, options.connectionId, options.domain);\n\tif (integration == null) {\n\t\t// A supplied connection or domain that no longer resolves is a broken target, not an empty account —\n\t\t// surface a no-connection warning + fetchFailed rather than a silent empty page.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(options.providerId, options.connectionId, options.domain);\n\t\treturn refusedPage(page, early.warnings, early.fetchFailed);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[gitHostOnlySurfaceWarning(options.providerId, undefined, options.connectionId, 'pull request reads')],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options.forceSync, options.connectionId);\n\n\tconst domain = ctx.domainForRead(integration, options.providerId, options.connectionId, options.domain);\n\t// With no repos this is an account-wide \"my PRs\" read; the repo-scoped core rejects an empty `repos`\n\t// input, so route to the account-wide, inherently user-scoped core instead (see drainPullRequests).\n\t// That provider-defined path takes no `pageSize`. A page-only request is handled by walking any opaque\n\t// continuations it supplies below. Do NOT synthesize a page-number cursor for it: cursor-based queries\n\t// (e.g. GitHub `involves:`) ignore a page number and would return their first page.\n\tconst accountWide = (options.repos?.length ?? 0) === 0;\n\tconst cursor = accountWide ? options.cursor : (options.cursor ?? pageToCursor(page));\n\n\tconst resolvedFilters = accountWide\n\t\t? resolveAccountWidePullRequestFilters(options.providerId, options.filters)\n\t\t: resolvePullRequestFilters(options.providerId, options.filters);\n\tif (resolvedFilters.unsupported) {\n\t\treturn refusedPage(\n\t\t\tpage,\n\t\t\t[\n\t\t\t\taccountWide\n\t\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(\n\t\t\t\t\t\t\toptions.providerId,\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t\toptions.filters ?? [],\n\t\t\t\t\t\t)\n\t\t\t\t\t: unsupportedFiltersWarning(options.providerId, domain, options.connectionId),\n\t\t\t],\n\t\t\ttrue,\n\t\t);\n\t}\n\n\t// Everything but the cursor, shared by the initial read and the drain's re-reads below so the two cannot\n\t// drift. `summary` is the reason this matters beyond tidiness: a re-read that dropped it would both\n\t// revert to the full payload — on the very path that issues the most requests — and return rows of a\n\t// different shape than the first page, since a summary row reports `headCommit`/`commitCount` as absent.\n\t// `summary` is pinned true here rather than forwarded: this branch has never had a full projection to\n\t// fall back to (see the option's doc), so honoring an explicit `false` would promise a widening the read\n\t// cannot deliver.\n\tconst accountWideInput = {\n\t\tstate: options.states,\n\t\tincludeReviewRequested: options.includeReviewRequested ?? false,\n\t\tfilters: resolvedFilters.filters,\n\t\tsummary: true,\n\t};\n\t// `filters` scopes the read to the current user (the core resolves the account for these), so it returns\n\t// the user's PRs. `pageSize` rides along so PagingMode.Repo hosts (GitLab, Bitbucket, Azure), whose\n\t// per-repo cursor path ignores a synthesized page-number cursor, honor the requested size.\n\tconst scopedInput = {\n\t\tstate: options.states,\n\t\tfilters: resolvedFilters.filters,\n\t\tpageSize: options.itemsPerPage,\n\t\tsummary: options.summary,\n\t};\n\tconst readPage = (pageCursor: string | undefined, requestedPage?: number) =>\n\t\trunCaptured(\n\t\t\toptions.providerId,\n\t\t\tdomain,\n\t\t\toptions.connectionId,\n\t\t\t() =>\n\t\t\t\taccountWide\n\t\t\t\t\t? integration.getMyPullRequestsForUserResult(\n\t\t\t\t\t\t\t{ ...accountWideInput, cursor: pageCursor },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t)\n\t\t\t\t\t: integration.getMyPullRequestsForReposResult(\n\t\t\t\t\t\t\toptions.repos ?? [],\n\t\t\t\t\t\t\t{ ...scopedInput, cursor: pageCursor, page: requestedPage },\n\t\t\t\t\t\t\toptions.connectionId,\n\t\t\t\t\t\t),\n\t\t\t{\n\t\t\t\twarnOnMissingSession: warnOnMissingSessionForDomain(options.providerId, options.domain),\n\t\t\t},\n\t\t);\n\tconst { value, warning } = await readPage(cursor, options.page != null ? page : undefined);\n\n\tlet items = value?.values ?? [];\n\t// Cursor-only account-wide reads start at page 1; a page-only request is advanced through opaque\n\t// continuations below. Repo-scoped reads report the requested page unless the provider reports its own.\n\tlet paged = toProviderPageInfo(items.length, value?.paging);\n\tlet allMetadata = value?.metadata;\n\t// Convert the SDK collection metadata into scope-aware warnings + failure/truncation flags, appending\n\t// them to any captured thrown-error warning without discarding the partial result's items.\n\tconst warnings = warning != null ? [warning] : [];\n\tlet pageFetchFailed = warning != null && value == null;\n\n\t// Cursor-only reads ignore a synthesized page-number cursor, so a page-only request is advanced through\n\t// the provider's own continuations (see drainToRequestedPage).\n\tif (\n\t\t(accountWide || providersMetadata[options.providerId]?.pullRequestsPagingMode === PagingMode.Repos) &&\n\t\toptions.page != null &&\n\t\tpage > 1 &&\n\t\toptions.cursor == null &&\n\t\tpaged.page.currentPage === 1\n\t) {\n\t\tconst drained = await drainToRequestedPage<ProviderPullRequest | PullRequest>(\n\t\t\t{ items: items, paged: paged, metadata: allMetadata, fetchFailed: pageFetchFailed },\n\t\t\t{\n\t\t\t\trequestedPage: page,\n\t\t\t\titemsPerPage: options.itemsPerPage,\n\t\t\t\twarnings: warnings,\n\t\t\t\t// A drain advances through provider continuations, so only the cursor positions these reads.\n\t\t\t\treadPage: (cursor: string) => readPage(cursor),\n\t\t\t},\n\t\t);\n\t\titems = drained.items;\n\t\tpaged = drained.paged;\n\t\tallMetadata = drained.metadata;\n\t\tpageFetchFailed = drained.fetchFailed;\n\t}\n\n\tconst assessment = mergeAssessmentInto(warnings, options.providerId, domain, options.connectionId, allMetadata);\n\t// Never advertise `hasMore` without a continuation the caller can act on. The account-wide read has no\n\t// page to synthesize, and neither does a host that doesn't honor a page number — see\n\t// {@link IntegrationService.isPageNumberAdvanceable} for which do and why absence of a declared mode\n\t// does NOT count as one.\n\tconst pageAdvanceable =\n\t\t!accountWide && isPageNumberAdvanceable(providersMetadata[options.providerId]?.pullRequestsPagingMode);\n\tconst continuation = resolveContinuation(paged, pageAdvanceable ? paged.page.currentPage + 1 : undefined);\n\t// A single-page provider read that couldn't confirm completeness sets `paging.truncated`; surface it\n\t// as a terminal `page.truncated` (not `hasMore`, which has no cursor to advance) so the caller knows\n\t// the page may be incomplete. Metadata incompleteness is an independent source of the same signal.\n\tconst truncated = continuation.truncated || assessment.truncated;\n\tif (truncated && warnings.length === 0) {\n\t\t// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise. Ordinary\n\t\t// continuation is already expressed by `hasMore`/`cursor`, so reaching here means this page itself\n\t\t// couldn't be completed — raising anything would return the same page.\n\t\twarnings.push(\n\t\t\ttruncationWarning(\n\t\t\t\toptions.providerId,\n\t\t\t\tdomain,\n\t\t\t\toptions.connectionId,\n\t\t\t\t'Pull request',\n\t\t\t\tassessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',\n\t\t\t),\n\t\t);\n\t}\n\t// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.\n\treconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);\n\tconst viewers = await getPullRequestViewers(integration, options.connectionId, items);\n\tconst projection = accountWide ? 'account-summary' : options.summary ? 'repos-summary' : 'repos';\n\treturn {\n\t\t// Normalize the raw rows to the GitLens-owned shape at the surface boundary.\n\t\titems: items.map((pr, i) =>\n\t\t\ttoPullRequestRow(pr, integration, { currentAccount: viewers[i], projection: projection }),\n\t\t),\n\t\twarnings: warnings,\n\t\t// The account-wide read can't take a page size, so don't echo the requested `itemsPerPage` as if it\n\t\t// had been applied — report what came back.\n\t\tpage: {\n\t\t\t...paged.page,\n\t\t\t// Positional, per ProviderPageInfo.currentPage: the drain (above) already advanced `paged.page`,\n\t\t\t// and a caller-threaded cursor advanced the provider, so neither leaves a cursor-only read stuck\n\t\t\t// reporting page 1.\n\t\t\tcurrentPage: resolveCurrentPage({\n\t\t\t\tproviderPage: paged.page.currentPage,\n\t\t\t\trequestedPage: page,\n\t\t\t\tsuppliedCursor: options.cursor,\n\t\t\t\tpageAdvanceable: pageAdvanceable,\n\t\t\t}),\n\t\t\titemsPerPage: accountWide ? items.length : paged.page.itemsPerPage,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\thasMore: continuation.hasMore,\n\t\tcursor: continuation.cursor,\n\t\t// A metadata failure means items are incomplete even when the read didn't throw; a thrown error with\n\t\t// no recovered value is the pre-existing failure case.\n\t\tfetchFailed: assessment.fetchFailed || pageFetchFailed || undefined,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -2,9 +2,9 @@ import { mapBounded } from "../../../utils/promise.js";
|
|
|
2
2
|
import "../constants.js";
|
|
3
3
|
import { appendDedupedWarning } from "../results.js";
|
|
4
4
|
import { isGitHostIntegration, isIssuesHostIntegrationId } from "../utils/integration.utils.js";
|
|
5
|
-
import {
|
|
5
|
+
import { toPullRequestRow } from "../providers/models.js";
|
|
6
6
|
import { gitHostOnlySurfaceWarning, noConnectionWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning } from "./warnings.js";
|
|
7
|
-
import { drainPullRequests,
|
|
7
|
+
import { drainPullRequests, getPullRequestViewers, resolvePullRequestSweepTargets } from "./drains.js";
|
|
8
8
|
import { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from "./filters.js";
|
|
9
9
|
//#region ../plus/integrations/src/reads/sweeps.ts
|
|
10
10
|
/**
|
|
@@ -43,10 +43,14 @@ async function sweepTarget(ctx, options, target, attributeUnavailableProviders)
|
|
|
43
43
|
const resolved = accountWide ? resolveAccountWidePullRequestFilters(id, requestedFilters) : resolvePullRequestFilters(id, requestedFilters);
|
|
44
44
|
if (resolved.unsupported) return rejectedTarget([accountWide ? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? []) : unsupportedFiltersWarning(id, domain, connectionId)]);
|
|
45
45
|
const drain = await drainPullRequests(integration, id, domain, repos, options?.states, resolved.filters, options, connectionId, maxPages, attributeUnavailableProviders);
|
|
46
|
-
const
|
|
46
|
+
const viewers = await getPullRequestViewers(integration, connectionId, drain.items);
|
|
47
|
+
const projection = accountWide ? options?.includeReviews ? "account" : "account-summary" : "repos";
|
|
47
48
|
return {
|
|
48
49
|
...drain,
|
|
49
|
-
items: drain.items.map((pr) =>
|
|
50
|
+
items: drain.items.map((pr, i) => toPullRequestRow(pr, integration, {
|
|
51
|
+
currentAccount: viewers[i],
|
|
52
|
+
projection
|
|
53
|
+
})),
|
|
50
54
|
providerId: id
|
|
51
55
|
};
|
|
52
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sweeps.js","names":[],"sources":["../../../../../plus/integrations/src/reads/sweeps.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport type {\n\tClosedPullRequestSweepOptions,\n\tProviderSweepTarget,\n\tProviderSweepTargetEvent,\n\tPullRequestSweepOptions,\n} from '../manager.js';\nimport { fromProviderPullRequest } from '../providers/models.js';\nimport type { ProviderSweepResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { drainPullRequests, getCurrentAccountIdentity, resolvePullRequestSweepTargets } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tnoConnectionWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\n/**\n * The all-pages pull request reads: one drain per provider target, aggregated into a single sweep result.\n *\n * A sweep exposes no continuation of its own — it drains everything it can reach — so its whole contract lives in\n * the completeness signals it returns: `page.allPages` asserts every page of every target was read,\n * `page.truncated` says pages remain unread, and `failedProviderIds`/`incompleteProviderIds` separate a provider\n * that produced nothing from one that produced a slice with a gap. That split is why the aggregation below can't\n * be collapsed into the generic fan-out merge the hierarchy reads use.\n */\n\n/** One provider target's drained slice, before the cross-provider aggregation. */\ninterface SweepSlice {\n\titems: PullRequestShape[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tproviderId: IntegrationIds;\n\tfailedProvider: boolean;\n}\n\n/**\n * Drain ONE sweep target. Extracted from the fan-out so the fan-out callback has a single exit: every\n * per-target observation (see `onTargetSettled`) is then reported in one place instead of at each of this\n * function's several early returns, where a missed branch would silently drop a provider's attribution.\n *\n * `undefined` means the target resolved to no reachable connection and is deliberately not attributed in the\n * aggregate result.\n */\nasync function sweepTarget(\n\tctx: ProviderReadContext,\n\toptions: PullRequestSweepOptions | undefined,\n\ttarget: ProviderSweepTarget,\n\tattributeUnavailableProviders: boolean,\n): Promise<SweepSlice | undefined> {\n\tconst { providerId: id, connectionId, domain: requestedDomain } = target;\n\tconst repos = options?.repos ?? [];\n\tconst maxPages = options?.maxPages ?? 100;\n\t/** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */\n\tconst rejectedTarget = (warnings: ProviderWarning[]): SweepSlice => ({\n\t\titems: [],\n\t\twarnings: warnings,\n\t\tfetchFailed: true,\n\t\ttruncated: false,\n\t\tproviderId: id,\n\t\tfailedProvider: true,\n\t});\n\n\tif (isIssuesHostIntegrationId(id)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(id, connectionId, requestedDomain);\n\tif (integration == null) {\n\t\t// A requested connection that can't be resolved is a broken connection — surface it as a\n\t\t// warning + fetchFailed rather than dropping the provider's slice silently.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(id, connectionId, requestedDomain);\n\t\tif (early.warnings.length === 0 && !attributeUnavailableProviders) return undefined;\n\n\t\treturn rejectedTarget(\n\t\t\tearly.warnings.length !== 0 ? early.warnings : [noConnectionWarning(id, requestedDomain, connectionId)],\n\t\t);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options?.forceSync, connectionId);\n\n\tconst domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);\n\tconst accountWide = repos.length === 0;\n\tconst requestedFilters = target.filters ?? options?.filters;\n\tconst resolved = accountWide\n\t\t? resolveAccountWidePullRequestFilters(id, requestedFilters)\n\t\t: resolvePullRequestFilters(id, requestedFilters);\n\tif (resolved.unsupported) {\n\t\treturn rejectedTarget([\n\t\t\taccountWide\n\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])\n\t\t\t\t: unsupportedFiltersWarning(id, domain, connectionId),\n\t\t]);\n\t}\n\n\tconst drain = await drainPullRequests(\n\t\tintegration,\n\t\tid,\n\t\tdomain,\n\t\trepos,\n\t\toptions?.states,\n\t\tresolved.filters,\n\t\toptions,\n\t\tconnectionId,\n\t\tmaxPages,\n\t\tattributeUnavailableProviders,\n\t);\n\tconst currentAccount = drain.items.some(pr => pr.author != null)\n\t\t? await getCurrentAccountIdentity(integration, connectionId)\n\t\t: undefined;\n\t// Normalize the raw provider-apis PRs to the GitLens-owned shape here, where the per-provider\n\t// `integration` (the mapper's provider reference) is in scope; the aggregation below only sees drains.\n\treturn {\n\t\t...drain,\n\t\titems: drain.items.map(pr => fromProviderPullRequest(pr, integration, { currentAccount: currentAccount })),\n\t\tproviderId: id,\n\t};\n}\n\n/**\n * How a target ended, as the consumer buckets it. `failedProvider` outranks `fetchFailed` because a target\n * whose provider failed produced no slice to be partial about, and `undefined` is the target that resolved to\n * no reachable connection at all.\n */\nfunction sliceOutcome(slice: SweepSlice | undefined): ProviderSweepTargetEvent['outcome'] {\n\tif (slice == null) return 'skipped';\n\tif (slice.failedProvider) return 'failed-provider';\n\tif (slice.fetchFailed) return 'fetch-failed';\n\treturn 'ok';\n}\n\n/**\n * Per-target reporting for a sweep's `onTargetSettled`, opened once per sweep: this stamps the fan-out start,\n * the returned function stamps one target's start, and the function IT returns is the only thing that can\n * report that target. The timestamps therefore never cross a boundary, so there is no pair of interchangeable\n * numbers for a call site to transpose, and the whole reporting concern is one value the fan-out either has or\n * does not. That is also what keeps the option free when it is omitted: with no observer there is nothing to\n * open, so no clock is read and no reporting object exists — do not \"simplify\" that into an unconditional\n * open, because a host whose perf gate is off (the default) is entitled to pay nothing for it.\n *\n * The try/catch is what makes the observer observation-only: called from the fan-out's success path, a throwing\n * callback would otherwise propagate out of the `mapBounded` task and reject the entire sweep — corrupting the\n * read, not just the metric. Swallowed silently; the consumer owns its own aggregation. The `void`-typed\n * callback also admits an `async` consumer, whose rejection is created only after `observe` returns — the\n * `Promise.resolve(...).catch` swallows that too, so it cannot escape as an unhandled rejection.\n *\n * The domain is resolved here rather than carried out of {@link sweepTarget}, so every target reports it by the\n * same rule no matter how far it got. `resolveDomainForRead` needs no integration instance, which is what makes\n * that possible: a target rejected by the first guard resolves the same host a fully drained one does.\n */\nfunction startSweepReporting(\n\tctx: ProviderReadContext,\n\tobserve: (event: ProviderSweepTargetEvent) => void | PromiseLike<unknown>,\n) {\n\tconst fanOutStartedAt = performance.now();\n\n\treturn function beginTarget(target: ProviderSweepTarget) {\n\t\tconst startedAt = performance.now();\n\n\t\treturn function reportSettled(slice: SweepSlice | undefined) {\n\t\t\ttry {\n\t\t\t\tvoid Promise.resolve(\n\t\t\t\t\tobserve({\n\t\t\t\t\t\tproviderId: target.providerId,\n\t\t\t\t\t\tdomain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain),\n\t\t\t\t\t\tconnectionId: target.connectionId,\n\t\t\t\t\t\tcount: slice?.items.length ?? 0,\n\t\t\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t\t\tqueueWaitMs: startedAt - fanOutStartedAt,\n\t\t\t\t\t\toutcome: sliceOutcome(slice),\n\t\t\t\t\t\ttruncated: slice?.truncated ?? false,\n\t\t\t\t\t}),\n\t\t\t\t).catch(() => {});\n\t\t\t} catch {}\n\t\t};\n\t};\n}\n\nexport async function sweepPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: PullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\tconst { targets, attributeUnavailableProviders } = resolvePullRequestSweepTargets(options);\n\n\tconst observe = options?.onTargetSettled;\n\tconst beginTarget = observe != null ? startSweepReporting(ctx, observe) : undefined;\n\n\tconst results = await mapBounded(targets, providerFanOutConcurrency, async target => {\n\t\tconst reportSettled = beginTarget?.(target);\n\t\tconst slice = await sweepTarget(ctx, options, target, attributeUnavailableProviders);\n\t\treportSettled?.(slice);\n\t\treturn slice;\n\t});\n\n\tconst items: PullRequestShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst failedProviderIds = new Set<IntegrationIds>();\n\tconst incompleteProviderIds = new Set<IntegrationIds>();\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tfor (const drain of results) {\n\t\tif (drain == null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\titems.push(...drain.items);\n\t\tfor (const w of drain.warnings) {\n\t\t\tappendDedupedWarning(warnings, w);\n\t\t}\n\t\tif (drain.fetchFailed) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (drain.failedProvider) {\n\t\t\tfailedProviderIds.add(drain.providerId);\n\t\t} else if (drain.fetchFailed || drain.truncated) {\n\t\t\tincompleteProviderIds.add(drain.providerId);\n\t\t}\n\t\tif (drain.truncated) {\n\t\t\ttruncated = true;\n\t\t}\n\t}\n\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\t// `allPages` asserts completeness — it must be false when any provider truncated (a single-page\n\t\t// account-wide read that couldn't confirm it drained everything) OR a drain aborted on a read\n\t\t// failure (its slice is incomplete). Either way the sweep did not read every page.\n\t\tpage: {\n\t\t\tcurrentPage: 1,\n\t\t\titemsPerPage: items.length,\n\t\t\tallPages: !truncated && !fetchFailed,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\t// A sweep drains every page itself and exposes no cursor to resume — so `hasMore` must be false even\n\t\t// when the read was incomplete. Terminal incompleteness is expressed through `page.truncated` +\n\t\t// `allPages: false` + warnings; setting `hasMore: true` here would make a consumer that drains while\n\t\t// `hasMore` re-run the identical sweep forever with no cursor to advance.\n\t\thasMore: false,\n\t\tfetchFailed: fetchFailed || undefined,\n\t\tfailedProviderIds: [...failedProviderIds],\n\t\tincompleteProviderIds: [...incompleteProviderIds],\n\t};\n}\n\n/**\n * Closed/merged counterpart of {@link sweepPullRequests}, feeding Kepler's Kanban \"done\" column. Applies\n * the native cross-provider state filter (`Closed` + `Merged`) so it works beyond GitHub.\n */\nexport function sweepClosedPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: ClosedPullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\treturn sweepPullRequests(ctx, { ...options, states: ['closed', 'merged'] });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sweeps.js","names":[],"sources":["../../../../../plus/integrations/src/reads/sweeps.ts"],"sourcesContent":["import type { PullRequestShape } from '@gitlens/git/models/pullRequest.js';\nimport { mapBounded } from '@gitlens/utils/promise.js';\nimport type { IntegrationIds } from '../constants.js';\nimport { providerFanOutConcurrency } from '../constants.js';\nimport type {\n\tClosedPullRequestSweepOptions,\n\tProviderSweepTarget,\n\tProviderSweepTargetEvent,\n\tPullRequestSweepOptions,\n} from '../manager.js';\nimport { toPullRequestRow } from '../providers/models.js';\nimport type { ProviderSweepResult, ProviderWarning } from '../results.js';\nimport { appendDedupedWarning } from '../results.js';\nimport { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integration.utils.js';\nimport type { ProviderReadContext } from './context.js';\nimport { drainPullRequests, getPullRequestViewers, resolvePullRequestSweepTargets } from './drains.js';\nimport { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';\nimport {\n\tgitHostOnlySurfaceWarning,\n\tnoConnectionWarning,\n\tunsupportedAccountWidePullRequestFiltersWarning,\n\tunsupportedFiltersWarning,\n} from './warnings.js';\n\n/**\n * The all-pages pull request reads: one drain per provider target, aggregated into a single sweep result.\n *\n * A sweep exposes no continuation of its own — it drains everything it can reach — so its whole contract lives in\n * the completeness signals it returns: `page.allPages` asserts every page of every target was read,\n * `page.truncated` says pages remain unread, and `failedProviderIds`/`incompleteProviderIds` separate a provider\n * that produced nothing from one that produced a slice with a gap. That split is why the aggregation below can't\n * be collapsed into the generic fan-out merge the hierarchy reads use.\n */\n\n/** One provider target's drained slice, before the cross-provider aggregation. */\ninterface SweepSlice {\n\titems: PullRequestShape[];\n\twarnings: ProviderWarning[];\n\tfetchFailed: boolean;\n\ttruncated: boolean;\n\tproviderId: IntegrationIds;\n\tfailedProvider: boolean;\n}\n\n/**\n * Drain ONE sweep target. Extracted from the fan-out so the fan-out callback has a single exit: every\n * per-target observation (see `onTargetSettled`) is then reported in one place instead of at each of this\n * function's several early returns, where a missed branch would silently drop a provider's attribution.\n *\n * `undefined` means the target resolved to no reachable connection and is deliberately not attributed in the\n * aggregate result.\n */\nasync function sweepTarget(\n\tctx: ProviderReadContext,\n\toptions: PullRequestSweepOptions | undefined,\n\ttarget: ProviderSweepTarget,\n\tattributeUnavailableProviders: boolean,\n): Promise<SweepSlice | undefined> {\n\tconst { providerId: id, connectionId, domain: requestedDomain } = target;\n\tconst repos = options?.repos ?? [];\n\tconst maxPages = options?.maxPages ?? 100;\n\t/** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */\n\tconst rejectedTarget = (warnings: ProviderWarning[]): SweepSlice => ({\n\t\titems: [],\n\t\twarnings: warnings,\n\t\tfetchFailed: true,\n\t\ttruncated: false,\n\t\tproviderId: id,\n\t\tfailedProvider: true,\n\t});\n\n\tif (isIssuesHostIntegrationId(id)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tconst integration = await ctx.getIntegrationForRead(id, connectionId, requestedDomain);\n\tif (integration == null) {\n\t\t// A requested connection that can't be resolved is a broken connection — surface it as a\n\t\t// warning + fetchFailed rather than dropping the provider's slice silently.\n\t\tconst early = ctx.earlyReturnConnectionWarnings(id, connectionId, requestedDomain);\n\t\tif (early.warnings.length === 0 && !attributeUnavailableProviders) return undefined;\n\n\t\treturn rejectedTarget(\n\t\t\tearly.warnings.length !== 0 ? early.warnings : [noConnectionWarning(id, requestedDomain, connectionId)],\n\t\t);\n\t}\n\tif (!isGitHostIntegration(integration)) {\n\t\treturn rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);\n\t}\n\n\tawait ctx.forceRefreshIfRequested(integration, options?.forceSync, connectionId);\n\n\tconst domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);\n\tconst accountWide = repos.length === 0;\n\tconst requestedFilters = target.filters ?? options?.filters;\n\tconst resolved = accountWide\n\t\t? resolveAccountWidePullRequestFilters(id, requestedFilters)\n\t\t: resolvePullRequestFilters(id, requestedFilters);\n\tif (resolved.unsupported) {\n\t\treturn rejectedTarget([\n\t\t\taccountWide\n\t\t\t\t? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])\n\t\t\t\t: unsupportedFiltersWarning(id, domain, connectionId),\n\t\t]);\n\t}\n\n\tconst drain = await drainPullRequests(\n\t\tintegration,\n\t\tid,\n\t\tdomain,\n\t\trepos,\n\t\toptions?.states,\n\t\tresolved.filters,\n\t\toptions,\n\t\tconnectionId,\n\t\tmaxPages,\n\t\tattributeUnavailableProviders,\n\t);\n\tconst viewers = await getPullRequestViewers(integration, connectionId, drain.items);\n\t// Mirrors the projection `drainPullRequests` requests.\n\tconst projection = accountWide ? (options?.includeReviews ? 'account' : 'account-summary') : 'repos';\n\t// Normalize the raw rows to the GitLens-owned shape here, where the per-provider `integration` (the\n\t// mapper's provider reference) is in scope; the aggregation below only sees drains.\n\treturn {\n\t\t...drain,\n\t\titems: drain.items.map((pr, i) =>\n\t\t\ttoPullRequestRow(pr, integration, { currentAccount: viewers[i], projection: projection }),\n\t\t),\n\t\tproviderId: id,\n\t};\n}\n\n/**\n * How a target ended, as the consumer buckets it. `failedProvider` outranks `fetchFailed` because a target\n * whose provider failed produced no slice to be partial about, and `undefined` is the target that resolved to\n * no reachable connection at all.\n */\nfunction sliceOutcome(slice: SweepSlice | undefined): ProviderSweepTargetEvent['outcome'] {\n\tif (slice == null) return 'skipped';\n\tif (slice.failedProvider) return 'failed-provider';\n\tif (slice.fetchFailed) return 'fetch-failed';\n\treturn 'ok';\n}\n\n/**\n * Per-target reporting for a sweep's `onTargetSettled`, opened once per sweep: this stamps the fan-out start,\n * the returned function stamps one target's start, and the function IT returns is the only thing that can\n * report that target. The timestamps therefore never cross a boundary, so there is no pair of interchangeable\n * numbers for a call site to transpose, and the whole reporting concern is one value the fan-out either has or\n * does not. That is also what keeps the option free when it is omitted: with no observer there is nothing to\n * open, so no clock is read and no reporting object exists — do not \"simplify\" that into an unconditional\n * open, because a host whose perf gate is off (the default) is entitled to pay nothing for it.\n *\n * The try/catch is what makes the observer observation-only: called from the fan-out's success path, a throwing\n * callback would otherwise propagate out of the `mapBounded` task and reject the entire sweep — corrupting the\n * read, not just the metric. Swallowed silently; the consumer owns its own aggregation. The `void`-typed\n * callback also admits an `async` consumer, whose rejection is created only after `observe` returns — the\n * `Promise.resolve(...).catch` swallows that too, so it cannot escape as an unhandled rejection.\n *\n * The domain is resolved here rather than carried out of {@link sweepTarget}, so every target reports it by the\n * same rule no matter how far it got. `resolveDomainForRead` needs no integration instance, which is what makes\n * that possible: a target rejected by the first guard resolves the same host a fully drained one does.\n */\nfunction startSweepReporting(\n\tctx: ProviderReadContext,\n\tobserve: (event: ProviderSweepTargetEvent) => void | PromiseLike<unknown>,\n) {\n\tconst fanOutStartedAt = performance.now();\n\n\treturn function beginTarget(target: ProviderSweepTarget) {\n\t\tconst startedAt = performance.now();\n\n\t\treturn function reportSettled(slice: SweepSlice | undefined) {\n\t\t\ttry {\n\t\t\t\tvoid Promise.resolve(\n\t\t\t\t\tobserve({\n\t\t\t\t\t\tproviderId: target.providerId,\n\t\t\t\t\t\tdomain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain),\n\t\t\t\t\t\tconnectionId: target.connectionId,\n\t\t\t\t\t\tcount: slice?.items.length ?? 0,\n\t\t\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t\t\tqueueWaitMs: startedAt - fanOutStartedAt,\n\t\t\t\t\t\toutcome: sliceOutcome(slice),\n\t\t\t\t\t\ttruncated: slice?.truncated ?? false,\n\t\t\t\t\t}),\n\t\t\t\t).catch(() => {});\n\t\t\t} catch {}\n\t\t};\n\t};\n}\n\nexport async function sweepPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: PullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\tconst { targets, attributeUnavailableProviders } = resolvePullRequestSweepTargets(options);\n\n\tconst observe = options?.onTargetSettled;\n\tconst beginTarget = observe != null ? startSweepReporting(ctx, observe) : undefined;\n\n\tconst results = await mapBounded(targets, providerFanOutConcurrency, async target => {\n\t\tconst reportSettled = beginTarget?.(target);\n\t\tconst slice = await sweepTarget(ctx, options, target, attributeUnavailableProviders);\n\t\treportSettled?.(slice);\n\t\treturn slice;\n\t});\n\n\tconst items: PullRequestShape[] = [];\n\tconst warnings: ProviderWarning[] = [];\n\tconst failedProviderIds = new Set<IntegrationIds>();\n\tconst incompleteProviderIds = new Set<IntegrationIds>();\n\tlet fetchFailed = false;\n\tlet truncated = false;\n\tfor (const drain of results) {\n\t\tif (drain == null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\titems.push(...drain.items);\n\t\tfor (const w of drain.warnings) {\n\t\t\tappendDedupedWarning(warnings, w);\n\t\t}\n\t\tif (drain.fetchFailed) {\n\t\t\tfetchFailed = true;\n\t\t}\n\t\tif (drain.failedProvider) {\n\t\t\tfailedProviderIds.add(drain.providerId);\n\t\t} else if (drain.fetchFailed || drain.truncated) {\n\t\t\tincompleteProviderIds.add(drain.providerId);\n\t\t}\n\t\tif (drain.truncated) {\n\t\t\ttruncated = true;\n\t\t}\n\t}\n\n\treturn {\n\t\titems: items,\n\t\twarnings: warnings,\n\t\t// `allPages` asserts completeness — it must be false when any provider truncated (a single-page\n\t\t// account-wide read that couldn't confirm it drained everything) OR a drain aborted on a read\n\t\t// failure (its slice is incomplete). Either way the sweep did not read every page.\n\t\tpage: {\n\t\t\tcurrentPage: 1,\n\t\t\titemsPerPage: items.length,\n\t\t\tallPages: !truncated && !fetchFailed,\n\t\t\ttruncated: truncated || undefined,\n\t\t},\n\t\t// A sweep drains every page itself and exposes no cursor to resume — so `hasMore` must be false even\n\t\t// when the read was incomplete. Terminal incompleteness is expressed through `page.truncated` +\n\t\t// `allPages: false` + warnings; setting `hasMore: true` here would make a consumer that drains while\n\t\t// `hasMore` re-run the identical sweep forever with no cursor to advance.\n\t\thasMore: false,\n\t\tfetchFailed: fetchFailed || undefined,\n\t\tfailedProviderIds: [...failedProviderIds],\n\t\tincompleteProviderIds: [...incompleteProviderIds],\n\t};\n}\n\n/**\n * Closed/merged counterpart of {@link sweepPullRequests}, feeding Kepler's Kanban \"done\" column. Applies\n * the native cross-provider state filter (`Closed` + `Merged`) so it works beyond GitHub.\n */\nexport function sweepClosedPullRequests(\n\tctx: ProviderReadContext,\n\toptions?: ClosedPullRequestSweepOptions,\n): Promise<ProviderSweepResult<PullRequestShape>> {\n\treturn sweepPullRequests(ctx, { ...options, states: ['closed', 'merged'] });\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { IssueSorting } from "../../../git/models/issue.js";
|
|
2
2
|
import { PullRequestFilter, PullRequestSorting } from "../../../git/models/pullRequest.js";
|
|
3
3
|
import { IntegrationIds } from "../constants.js";
|
|
4
|
-
import { IssueFilter } from "../providerFilters.js";
|
|
5
4
|
import { ProviderWarning } from "../results.js";
|
|
5
|
+
import { IssueFilter } from "../providerFilters.js";
|
|
6
6
|
import { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from "./filters.js";
|
|
7
7
|
import { UnmergeableIssueSort, UnsupportedIssueSortRejection } from "./ordering.js";
|
|
8
8
|
//#region ../plus/integrations/src/reads/warnings.d.ts
|
|
@@ -15,18 +15,31 @@ import { UnmergeableIssueSort, UnsupportedIssueSortRejection } from "./ordering.
|
|
|
15
15
|
* refusal can't be phrased two ways in two reads.
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
|
-
* The single builder for a provider-neutral, non-auth warning:
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* The single builder for a provider-neutral, non-auth `kind: 'other'` warning: a contradictory or malformed
|
|
19
|
+
* request the caller fixes itself, or a read that couldn't confirm completeness. A refusal for a capability the
|
|
20
|
+
* provider lacks goes through {@link unsupportedWarning} instead, so a consumer can tell the two apart without
|
|
21
|
+
* reading `message`. Every `'other'` or `'unsupported'` warning the facade raises on its own terms goes through
|
|
22
|
+
* one of the two (directly, or via a named builder below that pins a recurring message), so the discriminant is
|
|
23
|
+
* assigned in one place. The warnings mapped from SDK collection metadata are the documented exception —
|
|
24
|
+
* `collectionMetadata.ts` builds those from the structured failure/omission it is given.
|
|
24
25
|
*
|
|
25
26
|
* The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived
|
|
26
27
|
* from the caught error's type instead and never come from here; see `ProviderWarningKind` and
|
|
27
28
|
* {@link noConnectionWarning}.
|
|
28
29
|
*/
|
|
29
30
|
export declare function otherWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, message: string): ProviderWarning;
|
|
31
|
+
/**
|
|
32
|
+
* Builds a `kind: 'unsupported'` warning: the provider — or this integration of it — lacks the capability the
|
|
33
|
+
* request asked for: a surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo
|
|
34
|
+
* discovery, current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state,
|
|
35
|
+
* search criterion or option combination it cannot express. Retrying the same request never succeeds; a
|
|
36
|
+
* different provider, scope or request may.
|
|
37
|
+
*
|
|
38
|
+
* NOT for malformed or contradictory caller input the caller fixes in the request itself (a duplicate batch key,
|
|
39
|
+
* invalid target numbers, an empty owner, an unusable scope), nor for incompleteness warnings; those are
|
|
40
|
+
* {@link otherWarning}.
|
|
41
|
+
*/
|
|
42
|
+
export declare function unsupportedWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, message: string): ProviderWarning;
|
|
30
43
|
/**
|
|
31
44
|
* Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the
|
|
32
45
|
* requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this
|
|
@@ -109,7 +122,9 @@ export declare function unsupportedPullRequestSearchCriteriaWarning(id: Integrat
|
|
|
109
122
|
*
|
|
110
123
|
* One builder for all three because they are one decision from the caller's side ("this search can't be run as
|
|
111
124
|
* asked") and the remedy differs only in what the message says. The rejection shape carries which case it is, so
|
|
112
|
-
* the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}).
|
|
125
|
+
* the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}). The kind
|
|
126
|
+
* follows the reason: the first two are `'unsupported'` capabilities; a contradictory set is caller input and
|
|
127
|
+
* stays `'other'`.
|
|
113
128
|
*/
|
|
114
129
|
export declare function unsupportedIssueSearchCriteriaWarning(id: IntegrationIds, domain: string | undefined, connectionId: string | undefined, rejection: IssueSearchCriteriaRejection): ProviderWarning;
|
|
115
130
|
/**
|
|
@@ -9,12 +9,13 @@ import { providersMetadata } from "../providers/models.js";
|
|
|
9
9
|
* refusal can't be phrased two ways in two reads.
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
12
|
-
* The single builder for a provider-neutral, non-auth warning:
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
12
|
+
* The single builder for a provider-neutral, non-auth `kind: 'other'` warning: a contradictory or malformed
|
|
13
|
+
* request the caller fixes itself, or a read that couldn't confirm completeness. A refusal for a capability the
|
|
14
|
+
* provider lacks goes through {@link unsupportedWarning} instead, so a consumer can tell the two apart without
|
|
15
|
+
* reading `message`. Every `'other'` or `'unsupported'` warning the facade raises on its own terms goes through
|
|
16
|
+
* one of the two (directly, or via a named builder below that pins a recurring message), so the discriminant is
|
|
17
|
+
* assigned in one place. The warnings mapped from SDK collection metadata are the documented exception —
|
|
18
|
+
* `collectionMetadata.ts` builds those from the structured failure/omission it is given.
|
|
18
19
|
*
|
|
19
20
|
* The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived
|
|
20
21
|
* from the caught error's type instead and never come from here; see `ProviderWarningKind` and
|
|
@@ -31,6 +32,27 @@ function otherWarning(id, domain, connectionId, message) {
|
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
35
|
+
* Builds a `kind: 'unsupported'` warning: the provider — or this integration of it — lacks the capability the
|
|
36
|
+
* request asked for: a surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo
|
|
37
|
+
* discovery, current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state,
|
|
38
|
+
* search criterion or option combination it cannot express. Retrying the same request never succeeds; a
|
|
39
|
+
* different provider, scope or request may.
|
|
40
|
+
*
|
|
41
|
+
* NOT for malformed or contradictory caller input the caller fixes in the request itself (a duplicate batch key,
|
|
42
|
+
* invalid target numbers, an empty owner, an unusable scope), nor for incompleteness warnings; those are
|
|
43
|
+
* {@link otherWarning}.
|
|
44
|
+
*/
|
|
45
|
+
function unsupportedWarning(id, domain, connectionId, message) {
|
|
46
|
+
return {
|
|
47
|
+
providerId: id,
|
|
48
|
+
domain,
|
|
49
|
+
connectionId,
|
|
50
|
+
message,
|
|
51
|
+
kind: "unsupported",
|
|
52
|
+
isAuth: false
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
34
56
|
* Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the
|
|
35
57
|
* requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this
|
|
36
58
|
* to tell a truly empty account apart from a broken connection.
|
|
@@ -47,11 +69,11 @@ function noConnectionWarning(id, domain, connectionId) {
|
|
|
47
69
|
}
|
|
48
70
|
/** Warning for an issue-tracker provider asked for a surface only a git host serves (repos, PRs, sweeps). */
|
|
49
71
|
function gitHostOnlySurfaceWarning(id, domain, connectionId, surface) {
|
|
50
|
-
return
|
|
72
|
+
return unsupportedWarning(id, domain, connectionId, `${surface} is not supported by '${id}'; use a git-host integration instead.`);
|
|
51
73
|
}
|
|
52
74
|
/** Warning for a git host asked for a surface only an issue tracker serves (resource → project issue reads). */
|
|
53
75
|
function issueTrackerOnlySurfaceWarning(id, connectionId, surface) {
|
|
54
|
-
return
|
|
76
|
+
return unsupportedWarning(id, void 0, connectionId, `${surface} is not supported by '${id}'; use an issue-tracker integration instead.`);
|
|
55
77
|
}
|
|
56
78
|
function recoveryForIncompleteReadCause(cause) {
|
|
57
79
|
switch (cause) {
|
|
@@ -123,16 +145,16 @@ function unsupportedNarrowingMessage(id, noun, requested, supported) {
|
|
|
123
145
|
/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */
|
|
124
146
|
function unsupportedAccountWideIssueFiltersWarning(id, domain, connectionId, filters) {
|
|
125
147
|
const supported = providersMetadata[id]?.supportedAccountWideIssueFilters ?? [];
|
|
126
|
-
return
|
|
148
|
+
return unsupportedWarning(id, domain, connectionId, unsupportedNarrowingMessage(id, "account-wide issue filters", filters.join(", "), supported.join(", ")));
|
|
127
149
|
}
|
|
128
150
|
/** Warning for an account-wide PR read whose requested relationship union cannot be expressed exactly. */
|
|
129
151
|
function unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, filters) {
|
|
130
152
|
const supported = providersMetadata[id]?.supportedAccountWidePullRequestFilters ?? [];
|
|
131
|
-
return
|
|
153
|
+
return unsupportedWarning(id, domain, connectionId, unsupportedNarrowingMessage(id, "account-wide pull request filters", filters.join(", "), supported.join(", ")));
|
|
132
154
|
}
|
|
133
155
|
/** Warning for a repo-scoped PR read whose requested filters the provider supports none of. */
|
|
134
156
|
function unsupportedFiltersWarning(id, domain, connectionId) {
|
|
135
|
-
return
|
|
157
|
+
return unsupportedWarning(id, domain, connectionId, `The requested pull request filters are not supported by '${id}'; skipped to avoid returning unfiltered results.`);
|
|
136
158
|
}
|
|
137
159
|
/**
|
|
138
160
|
* The one sentence every `unusable-scope` refusal uses — the five surfaces that validate a search scope (both
|
|
@@ -165,7 +187,7 @@ function unsupportedPullRequestSearchCriteriaWarning(id, domain, connectionId, r
|
|
|
165
187
|
break;
|
|
166
188
|
}
|
|
167
189
|
}
|
|
168
|
-
return
|
|
190
|
+
return unsupportedWarning(id, domain, connectionId, message);
|
|
169
191
|
}
|
|
170
192
|
function describePullRequestSearchCapabilities(capabilities) {
|
|
171
193
|
return [
|
|
@@ -187,7 +209,9 @@ function describePullRequestSearchCapabilities(capabilities) {
|
|
|
187
209
|
*
|
|
188
210
|
* One builder for all three because they are one decision from the caller's side ("this search can't be run as
|
|
189
211
|
* asked") and the remedy differs only in what the message says. The rejection shape carries which case it is, so
|
|
190
|
-
* the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}).
|
|
212
|
+
* the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}). The kind
|
|
213
|
+
* follows the reason: the first two are `'unsupported'` capabilities; a contradictory set is caller input and
|
|
214
|
+
* stays `'other'`.
|
|
191
215
|
*/
|
|
192
216
|
function unsupportedIssueSearchCriteriaWarning(id, domain, connectionId, rejection) {
|
|
193
217
|
let message;
|
|
@@ -200,9 +224,9 @@ function unsupportedIssueSearchCriteriaWarning(id, domain, connectionId, rejecti
|
|
|
200
224
|
message = unsupportedNarrowingMessage(id, "issue search criteria", rejection.criteria.join(", "), supported != null ? describeIssueSearchCapabilities(supported) : "");
|
|
201
225
|
break;
|
|
202
226
|
}
|
|
203
|
-
case "contradictory-relationships":
|
|
227
|
+
case "contradictory-relationships": return otherWarning(id, domain, connectionId, `The relationships \`any-assignee\` and \`unassigned\` are contradictory — they partition the scope between them, so no issue satisfies both; pass only one.`);
|
|
204
228
|
}
|
|
205
|
-
return
|
|
229
|
+
return unsupportedWarning(id, domain, connectionId, message);
|
|
206
230
|
}
|
|
207
231
|
/** The criteria a provider CAN express, for the "supported: …" half of a refusal message. */
|
|
208
232
|
function describeIssueSearchCapabilities(capabilities) {
|
|
@@ -234,7 +258,7 @@ function describeIssueSearchCapabilities(capabilities) {
|
|
|
234
258
|
* free to disagree with the check that refused.
|
|
235
259
|
*/
|
|
236
260
|
function unsupportedIssueSortWarning(id, domain, connectionId, rejection) {
|
|
237
|
-
return
|
|
261
|
+
return unsupportedWarning(id, domain, connectionId, unsupportedNarrowingMessage(id, "issue sort", rejection.requested, (rejection.supported ?? []).join(", ")));
|
|
238
262
|
}
|
|
239
263
|
/**
|
|
240
264
|
* Warning for a merged issue read asked to order by a key that no normalized issue carries.
|
|
@@ -264,7 +288,7 @@ function unmergeableIssueSortWarning(id, domain, connectionId, unmergeable, scop
|
|
|
264
288
|
one: "relationship"
|
|
265
289
|
}
|
|
266
290
|
}[scope];
|
|
267
|
-
return
|
|
291
|
+
return unsupportedWarning(id, domain, connectionId, `Cannot order by '${unmergeable.requested}' across several ${nouns.many}: this read merges their results, and a normalized issue carries no such field to merge on. Read one ${nouns.one} at a time, or order by a date, title, comment or reaction count.`);
|
|
268
292
|
}
|
|
269
293
|
/**
|
|
270
294
|
* The omission for a filtered issue search that hit the provider's RESULT CEILING: more issues matched than the
|
|
@@ -313,9 +337,9 @@ function pullRequestSearchCapResultWarning(id, domain, connectionId, totalCount,
|
|
|
313
337
|
}
|
|
314
338
|
/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */
|
|
315
339
|
function issuesUnsupportedWarning(id, domain, connectionId) {
|
|
316
|
-
return
|
|
340
|
+
return unsupportedWarning(id, domain, connectionId, `Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`);
|
|
317
341
|
}
|
|
318
342
|
//#endregion
|
|
319
|
-
export { cappedBesideBudgetWarning, gitHostOnlySurfaceWarning, incompleteReadWarning, issueSearchCapResultWarning, issueTrackerOnlySurfaceWarning, issuesUnsupportedWarning, noConnectionWarning, otherWarning, pullRequestSearchCapResultWarning, truncationWarning, unmergeableIssueSortWarning, unsupportedAccountWideIssueFiltersWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning, unsupportedIssueSearchCriteriaWarning, unsupportedIssueSortWarning, unsupportedPullRequestSearchCriteriaWarning, unusableSearchScopeMessage };
|
|
343
|
+
export { cappedBesideBudgetWarning, gitHostOnlySurfaceWarning, incompleteReadWarning, issueSearchCapResultWarning, issueTrackerOnlySurfaceWarning, issuesUnsupportedWarning, noConnectionWarning, otherWarning, pullRequestSearchCapResultWarning, truncationWarning, unmergeableIssueSortWarning, unsupportedAccountWideIssueFiltersWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning, unsupportedIssueSearchCriteriaWarning, unsupportedIssueSortWarning, unsupportedPullRequestSearchCriteriaWarning, unsupportedWarning, unusableSearchScopeMessage };
|
|
320
344
|
|
|
321
345
|
//# sourceMappingURL=warnings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warnings.js","names":[],"sources":["../../../../../plus/integrations/src/reads/warnings.ts"],"sourcesContent":["import type { IntegrationIds } from '../constants.js';\nimport type {\n\tIssueFilter,\n\tIssueSearchCapabilities,\n\tIssueSorting,\n\tPullRequestFilter,\n\tPullRequestSearchCapabilities,\n\tPullRequestSorting,\n} from '../providerFilters.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type { ProviderWarning, ProviderWarningOmissionRecovery } from '../results.js';\nimport type { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from './filters.js';\nimport type { UnmergeableIssueSort, UnsupportedIssueSortRejection } from './ordering.js';\n\n/**\n * The warnings the provider facade raises on its OWN terms, as opposed to the ones derived from a caught\n * provider error ({@link toProviderWarning}) or from SDK collection metadata (`collectionMetadata.ts`).\n *\n * These are pure message builders, kept together and out of the service so a read method reads as the decision\n * it makes (\"this surface doesn't apply, refuse it\") rather than as the wording of the refusal, and so the same\n * refusal can't be phrased two ways in two reads.\n */\n\n/**\n * The single builder for a provider-neutral, non-auth warning: an unsupported capability, a\n * contradictory/inexpressible request, or a read that couldn't confirm completeness. Every `kind: 'other'`\n * warning the facade raises on its own terms goes through here (directly, or via one of the named builders\n * below that pin a recurring message), so the discriminant is assigned in one place. The warnings mapped from\n * SDK collection metadata are the documented exception — `collectionMetadata.ts` builds those from the\n * structured failure/omission it is given.\n *\n * The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived\n * from the caught error's type instead and never come from here; see `ProviderWarningKind` and\n * {@link noConnectionWarning}.\n */\nexport function otherWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'other',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the\n * requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this\n * to tell a truly empty account apart from a broken connection.\n */\nexport function noConnectionWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tconnectionId != null\n\t\t\t\t? `Connection '${connectionId}' for '${id}' could not be resolved (deleted or invalid authentication).`\n\t\t\t\t: `No active connection for '${id}' could be resolved.`,\n\t\tkind: 'no-connection',\n\t\tisAuth: false,\n\t};\n}\n\n/** Warning for an issue-tracker provider asked for a surface only a git host serves (repos, PRs, sweeps). */\nexport function gitHostOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use a git-host integration instead.`,\n\t);\n}\n\n/** Warning for a git host asked for a surface only an issue tracker serves (resource → project issue reads). */\nexport function issueTrackerOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tundefined,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use an issue-tracker integration instead.`,\n\t);\n}\n\n/** Why a facade-driven read returned fewer results than requested. */\nexport type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' | 'scope-too-large';\n\nfunction recoveryForIncompleteReadCause(cause: IncompleteReadCause): ProviderWarningOmissionRecovery {\n\tswitch (cause) {\n\t\tcase 'page-budget':\n\t\t\treturn 'page-budget';\n\t\tcase 'scope-too-large':\n\t\t\treturn 'narrow-scope';\n\t\tcase 'interrupted':\n\t\tcase 'exhausted':\n\t\t\treturn 'none';\n\t\tdefault:\n\t\t\tcause satisfies never;\n\t\t\treturn 'none';\n\t}\n}\n\n/**\n * Builds the warning for a read that returned less than everything, carrying the two facts a consumer cannot\n * recover on its own: did the request succeed, and would anything fetch the rest?\n *\n * Every incompleteness warning the facade raises on its own terms goes through here, so no read can drift\n * into claiming success on a failure or offering a \"load more\" that cannot deliver. The omission it attaches\n * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care\n * which layer noticed the gap.\n *\n * No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed\n * omission can, so a consumer acting on `recovery` re-runs the read itself.\n */\nexport function incompleteReadWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\tconst warning = otherWarning(id, domain, connectionId, message);\n\tif (cause === 'interrupted') return warning;\n\n\treturn {\n\t\t...warning,\n\t\tomission: {\n\t\t\tkind: 'pagination-incomplete',\n\t\t\trecovery: recoveryForIncompleteReadCause(cause),\n\t\t},\n\t};\n}\n\n/**\n * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite\n * read that spans several provider searches rather than one surface.\n *\n * Deliberately not exported: every caller passes a literal, and publishing it would invite a consumer to switch\n * on a set that exists only to word a message.\n */\ntype TruncatedReadKind =\n\t| 'Pull request'\n\t| 'Issue'\n\t| 'Repository'\n\t| 'Organization'\n\t| 'Project'\n\t| 'Account-wide issue search'\n\t| 'Issue search'\n\t| 'Pull request search';\n\n/** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */\nexport function truncationWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n\t/**\n\t * Deliberately REQUIRED and not defaulted: a default would make one of these the silent fallback at any\n\t * call site that forgot it, and both of the claims it carries — \"the request succeeded\" and \"more can be\n\t * fetched\" — are ones this warning must never make by accident.\n\t */\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\treturn incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);\n}\n\n/**\n * The part of a drain that paging could not reach, when that drain ALSO stopped at its page budget with a usable\n * cursor — one facet of a composite read reaching the provider's ceiling while a sibling can still page. Reported\n * beside the `page-budget` {@link truncationWarning}, never in place of it: raising the budget returns more of the\n * rest, and nothing returns this part, so each warning speaks only for its own.\n *\n * The message names no mechanism. The fact behind it is `paging.truncated`, which a provider cap sets but so does\n * `hasMore` with no usable cursor (GitHub's `structuralIncompleteness`) and any per-repository truncation on the\n * repo-scoped path, so \"capped by the provider\" would name a cause we cannot confirm.\n */\nexport function cappedBesideBudgetWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n): ProviderWarning {\n\treturn incompleteReadWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${readKind} read for '${id}' could not reach part of its results by paging; raising the page budget will not return those.`,\n\t\t'exhausted',\n\t);\n}\n\nfunction truncationMessage(id: IntegrationIds, readKind: TruncatedReadKind, cause: IncompleteReadCause): string {\n\tswitch (cause) {\n\t\tcase 'interrupted':\n\t\t\t// Deliberately does not name a mechanism. This fires both when a page was lost mid-drain and when a\n\t\t\t// drain that latched an earlier scope failure later stopped for its own reasons; \"was interrupted\"\n\t\t\t// would be false in the second. What is true in both is that something failed and results are gone.\n\t\t\treturn `${readKind} read for '${id}' did not complete; some results are missing and the read reported a failure.`;\n\t\tcase 'page-budget':\n\t\t\treturn `${readKind} read for '${id}' stopped at its page budget; more results can be read by raising it.`;\n\t\tcase 'exhausted':\n\t\t\treturn `${readKind} read for '${id}' was truncated and cannot be continued; results may be incomplete.`;\n\t\tcase 'scope-too-large':\n\t\t\t// Says what is true — the scope is larger than a read of it — rather than blaming the provider for a\n\t\t\t// ceiling that is ours, and names the remedy that exists, since no retry or budget has one.\n\t\t\treturn `${readKind} read for '${id}' covers more than one read can return; narrow the scope to read the rest.`;\n\t}\n\t// No `default`: `IncompleteReadCause` is declared in this file, so `noImplicitReturns` already fails the\n\t// build here if a cause is added without its own wording. (`collectionOmissionMessage`'s `satisfies never`\n\t// is not the same case — that union is the SDK's, and can widen under a dependency bump.)\n}\n\n/**\n * The one sentence every \"you asked for a narrowing this provider can't express\" refusal uses.\n *\n * Shared because the three callers below differ only in a noun: the wording is consumer-visible, and three\n * hand-written copies drift three ways — including the `(supported: …)` clause, which exists to avoid emitting an\n * empty parenthetical and has to be the same guard in all of them.\n */\nfunction unsupportedNarrowingMessage(id: IntegrationIds, noun: string, requested: string, supported: string): string {\n\treturn `The requested ${noun} (${requested}) are not supported by '${id}'${supported.length > 0 ? ` (supported: ${supported})` : ''}; skipped to avoid returning a wider result than requested.`;\n}\n\n/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */\nexport function unsupportedAccountWideIssueFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: IssueFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWideIssueFilters ?? [];\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide issue filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for an account-wide PR read whose requested relationship union cannot be expressed exactly. */\nexport function unsupportedAccountWidePullRequestFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: PullRequestFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWidePullRequestFilters ?? [];\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide pull request filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for a repo-scoped PR read whose requested filters the provider supports none of. */\nexport function unsupportedFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`The requested pull request filters are not supported by '${id}'; skipped to avoid returning unfiltered results.`,\n\t);\n}\n\n/**\n * The one sentence every `unusable-scope` refusal uses — the five surfaces that validate a search scope (both\n * searches, both count probes, and issue broadening) differ only in the noun and in whether they name a count\n * key, so the reason a scope is unusable is written once. `prefix` is what the count probes put in front of it\n * to name their key.\n *\n * NAMES the offending values, because which failure mode applied is not deducible from the value: a quote makes\n * `git\"kraken` a request for the real and different org `gitkraken`, a space makes `my org` a search of `my`\n * filtered by the text `org`, and a value of only quotes or whitespace leaves no scope at all. The caller is the\n * only layer that knows which scope it meant.\n *\n * Says how to fix it rather than what was stripped: a consumer cannot mirror this rule (unlike free-text\n * sanitizing, whose rules are published for exactly that), so \"pass the scope as the provider names it\" is the\n * actionable half.\n */\nexport function unusableSearchScopeMessage(noun: string, scopes: readonly string[], prefix?: string): string {\n\treturn `${prefix != null ? `${prefix}: ` : ''}The requested ${noun} (${scopes.map(s => JSON.stringify(s)).join(', ')}) cannot be used as given: a scope name cannot contain quotes, or an inner space or control character, which a query would drop or read as a second qualifier — searching a different scope, or none. Pass the scope exactly as the provider names it.`;\n}\n\n/** Warning for a filtered pull-request search the provider cannot run as requested. */\nexport function unsupportedPullRequestSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: PullRequestSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered pull request search is not supported by '${id}'; use \\`listPullRequestsPage\\` for its own pull request reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedPullRequestSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'pull request search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describePullRequestSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn otherWarning(id, domain, connectionId, message);\n}\n\nfunction describePullRequestSearchCapabilities(capabilities: PullRequestSearchCapabilities): string {\n\treturn [\n\t\t...(capabilities.relationships.length ? [`relationships:${capabilities.relationships.join('|')}`] : []),\n\t\t...(capabilities.states.length ? [`states:${capabilities.states.join('|')}`] : []),\n\t\t...(capabilities.text ? ['text'] : []),\n\t\t...(capabilities.updatedAfter ? ['updatedAfter'] : []),\n\t\t...(capabilities.createdAfter ? ['createdAfter'] : []),\n\t\t...(capabilities.includeArchived ? ['includeArchived'] : []),\n\t\t...(capabilities.draft ? ['draft'] : []),\n\t\t...(capabilities.repositoryScope ? ['repository scope'] : []),\n\t\t...(capabilities.organizationScope ? ['organization scope'] : []),\n\t\t...(capabilities.sorts.length ? [`sorts:${capabilities.sorts.join('|')}`] : []),\n\t].join(', ');\n}\n\n/**\n * Warning for a filtered issue search whose criteria the provider can't serve — either it has no such search at\n * all, or it can't express what was asked for, or the set contradicts itself.\n *\n * One builder for all three because they are one decision from the caller's side (\"this search can't be run as\n * asked\") and the remedy differs only in what the message says. The rejection shape carries which case it is, so\n * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}).\n */\nexport function unsupportedIssueSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: IssueSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered issue search is not supported by '${id}'; use \\`listIssuesPage\\` for its own issue reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedIssueSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'issue search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describeIssueSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'contradictory-relationships':\n\t\t\tmessage = `The relationships \\`any-assignee\\` and \\`unassigned\\` are contradictory — they partition the scope between them, so no issue satisfies both; pass only one.`;\n\t\t\tbreak;\n\t}\n\t// No `default`: the union is declared alongside the validator, so `noImplicitReturns` fails the build here if\n\t// a rejection reason is added without its own wording.\n\n\treturn otherWarning(id, domain, connectionId, message);\n}\n\n/** The criteria a provider CAN express, for the \"supported: …\" half of a refusal message. */\nfunction describeIssueSearchCapabilities(capabilities: IssueSearchCapabilities): string {\n\tconst flags: [name: string, supported: boolean][] = [\n\t\t['text', capabilities.text],\n\t\t['labels', capabilities.labels],\n\t\t['milestone', capabilities.milestone],\n\t\t['updatedAfter', capabilities.updatedAfter],\n\t\t['createdAfter', capabilities.createdAfter],\n\t\t['withoutLinkedPullRequest', capabilities.withoutLinkedPullRequest],\n\t\t['state', capabilities.states],\n\t];\n\treturn [\n\t\t...capabilities.relationships.map(r => `relationships:${r}`),\n\t\t...flags.filter(([, supported]) => supported).map(([name]) => name),\n\t\t...capabilities.sorts.map(s => `sort:${s}`),\n\t].join(', ');\n}\n\n/**\n * Warning for an issue read asked to order by a key its provider can't express server-side.\n *\n * Separate from {@link unsupportedIssueSearchCriteriaWarning} because it serves the reads that take `sort` as an\n * OPTION rather than as a criterion — repo-scoped, account-wide, and issue-tracker — where there is no criteria\n * set to fold it into. The filtered search keeps reporting it as one more `unsupported-criteria` entry, so a\n * caller there still gets a single refusal listing everything at once.\n *\n * Takes the rejection the check produced rather than a re-read supported list: which of the two tables applies is\n * the caller's own branch (`repos` present or not), so reading it back here — or threading it by hand — would be\n * free to disagree with the check that refused.\n */\nexport function unsupportedIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: UnsupportedIssueSortRejection,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'issue sort', rejection.requested, (rejection.supported ?? []).join(', ')),\n\t);\n}\n\n/**\n * Warning for a merged issue read asked to order by a key that no normalized issue carries.\n *\n * The distinction this exists to make is not about the provider: the provider orders by `priority` perfectly well\n * within ONE project. It is about the read, which fans out over several and merges the results here, where the\n * only fields available are the ones {@link IssueShape} models. Serving the concatenated per-scope runs would look\n * ordered without being so, which is why this refuses instead — and why the message says WHY, since the same key\n * against the same provider succeeds on a single-scope read.\n *\n * What a read fans out OVER differs by read, hence the `scope`: repositories, projects, or — where one query is\n * issued per requested relationship and the runs are unioned within a single project (Jira) — the filters\n * themselves. Naming the wrong one would tell a caller to narrow something that isn't what merged.\n */\nexport function unmergeableIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tunmergeable: UnmergeableIssueSort,\n\tscope: 'repositories' | 'projects' | 'filters',\n): ProviderWarning {\n\t// Both forms are spelled out rather than depluralized at runtime: the message needs each, and\n\t// `'repositories'.replace(/s$/, '')` is `'repositorie'`.\n\tconst nouns = {\n\t\trepositories: { many: 'repositories', one: 'repository' },\n\t\tprojects: { many: 'projects', one: 'project' },\n\t\tfilters: { many: 'issue relationships', one: 'relationship' },\n\t}[scope];\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Cannot order by '${unmergeable.requested}' across several ${nouns.many}: this read merges their results, and a normalized issue carries no such field to merge on. Read one ${nouns.one} at a time, or order by a date, title, comment or reaction count.`,\n\t);\n}\n\n/**\n * The omission for a filtered issue search that hit the provider's RESULT CEILING: more issues matched than the\n * provider will ever serve for one query, however it is paged.\n *\n * Distinct from {@link truncationWarning} because this is the one incompleteness this read can quantify —\n * `totalCount` is how many matched, `limit` how many are reachable — which is what lets a consumer say \"19.240\n * matched, showing the 1.000 most recently updated\" instead of a bare \"results may be incomplete\". `recovery:\n * 'none'` because the items past the ceiling are UNREACHABLE, not merely unfetched: no budget, no retry, and no\n * cursor would return them. Narrowing the criteria is the only way through, which is a decision for the caller.\n *\n * The message names the ORDER, and has to now that ordering is an option: the reachable window is the top `limit`\n * under that key, so \"the 1.000 most recent\" is a true sentence only while the key is the default one. The same\n * value goes on the omission, so a consumer wording its own sentence doesn't have to remember what it asked for.\n *\n * Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording\n * rather than reporting a limit it invented.\n */\nexport function issueSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting,\n\t/** The provider proved the ceiling was reached without reporting how many matched; see `limitReached`. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Issue search',\n\t\tprovidersMetadata[id]?.issueSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/**\n * The shared body of the two ceiling warnings. Only a ceiling that was actually reached warns: either a reported\n * total above it, or a provider that proved the ceiling was reached but can't count past it — in which case the\n * omission carries the limit and the order but no `totalCount`, since any figure would be invented.\n */\nfunction searchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: 'Issue search' | 'Pull request search',\n\tlimit: number | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting | PullRequestSorting,\n\tlimitReached: boolean | undefined,\n): ProviderWarning | undefined {\n\tif (limit == null) return undefined;\n\n\t// Below the ceiling this warning would be a false claim; the caller's truncation had another cause.\n\tconst counted = totalCount != null && totalCount > limit;\n\tif (!counted && limitReached !== true) return undefined;\n\n\tconst [field, direction] = sort.split(':');\n\tconst matched = counted ? `matched ${totalCount} results` : `matched more than ${limit} results`;\n\treturn {\n\t\t...otherWarning(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t`${readKind} ${matched}, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`,\n\t\t),\n\t\tomission: {\n\t\t\tkind: 'provider-limit',\n\t\t\t// The items past the ceiling can't be fetched by anything, so never offer a \"load more\".\n\t\t\trecovery: 'none',\n\t\t\tlimit: limit,\n\t\t\t...(counted ? { totalCount: totalCount } : {}),\n\t\t\tsort: sort,\n\t\t},\n\t};\n}\n\n/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */\nexport function pullRequestSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: PullRequestSorting,\n\t/** See {@link issueSearchCapResultWarning}. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\t// Named order for the same reason as the issue twin: the reachable window is the first `limit` under THIS sort.\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Pull request search',\n\t\tprovidersMetadata[id]?.pullRequestSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */\nexport function issuesUnsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn otherWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`,\n\t);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"warnings.js","names":[],"sources":["../../../../../plus/integrations/src/reads/warnings.ts"],"sourcesContent":["import type { IntegrationIds } from '../constants.js';\nimport type {\n\tIssueFilter,\n\tIssueSearchCapabilities,\n\tIssueSorting,\n\tPullRequestFilter,\n\tPullRequestSearchCapabilities,\n\tPullRequestSorting,\n} from '../providerFilters.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type { ProviderWarning, ProviderWarningOmissionRecovery } from '../results.js';\nimport type { IssueSearchCriteriaRejection, PullRequestSearchCriteriaRejection } from './filters.js';\nimport type { UnmergeableIssueSort, UnsupportedIssueSortRejection } from './ordering.js';\n\n/**\n * The warnings the provider facade raises on its OWN terms, as opposed to the ones derived from a caught\n * provider error ({@link toProviderWarning}) or from SDK collection metadata (`collectionMetadata.ts`).\n *\n * These are pure message builders, kept together and out of the service so a read method reads as the decision\n * it makes (\"this surface doesn't apply, refuse it\") rather than as the wording of the refusal, and so the same\n * refusal can't be phrased two ways in two reads.\n */\n\n/**\n * The single builder for a provider-neutral, non-auth `kind: 'other'` warning: a contradictory or malformed\n * request the caller fixes itself, or a read that couldn't confirm completeness. A refusal for a capability the\n * provider lacks goes through {@link unsupportedWarning} instead, so a consumer can tell the two apart without\n * reading `message`. Every `'other'` or `'unsupported'` warning the facade raises on its own terms goes through\n * one of the two (directly, or via a named builder below that pins a recurring message), so the discriminant is\n * assigned in one place. The warnings mapped from SDK collection metadata are the documented exception —\n * `collectionMetadata.ts` builds those from the structured failure/omission it is given.\n *\n * The kinds that carry a programmatic remedy — `auth`, `rate-limit`, `not-found`, `no-connection` — are derived\n * from the caught error's type instead and never come from here; see `ProviderWarningKind` and\n * {@link noConnectionWarning}.\n */\nexport function otherWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'other',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `kind: 'unsupported'` warning: the provider — or this integration of it — lacks the capability the\n * request asked for: a surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo\n * discovery, current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state,\n * search criterion or option combination it cannot express. Retrying the same request never succeeds; a\n * different provider, scope or request may.\n *\n * NOT for malformed or contradictory caller input the caller fixes in the request itself (a duplicate batch key,\n * invalid target numbers, an empty owner, an unusable scope), nor for incompleteness warnings; those are\n * {@link otherWarning}.\n */\nexport function unsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage: message,\n\t\tkind: 'unsupported',\n\t\tisAuth: false,\n\t};\n}\n\n/**\n * Builds a `no-connection` warning for a per-connection read that resolved neither a session nor an error: the\n * requested `connectionId` no longer resolves (deleted, or its authentication is invalid). Consumers use this\n * to tell a truly empty account apart from a broken connection.\n */\nexport function noConnectionWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId?: string,\n): ProviderWarning {\n\treturn {\n\t\tproviderId: id,\n\t\tdomain: domain,\n\t\tconnectionId: connectionId,\n\t\tmessage:\n\t\t\tconnectionId != null\n\t\t\t\t? `Connection '${connectionId}' for '${id}' could not be resolved (deleted or invalid authentication).`\n\t\t\t\t: `No active connection for '${id}' could be resolved.`,\n\t\tkind: 'no-connection',\n\t\tisAuth: false,\n\t};\n}\n\n/** Warning for an issue-tracker provider asked for a surface only a git host serves (repos, PRs, sweeps). */\nexport function gitHostOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use a git-host integration instead.`,\n\t);\n}\n\n/** Warning for a git host asked for a surface only an issue tracker serves (resource → project issue reads). */\nexport function issueTrackerOnlySurfaceWarning(\n\tid: IntegrationIds,\n\tconnectionId: string | undefined,\n\tsurface: string,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tundefined,\n\t\tconnectionId,\n\t\t`${surface} is not supported by '${id}'; use an issue-tracker integration instead.`,\n\t);\n}\n\n/** Why a facade-driven read returned fewer results than requested. */\nexport type IncompleteReadCause = 'interrupted' | 'page-budget' | 'exhausted' | 'scope-too-large';\n\nfunction recoveryForIncompleteReadCause(cause: IncompleteReadCause): ProviderWarningOmissionRecovery {\n\tswitch (cause) {\n\t\tcase 'page-budget':\n\t\t\treturn 'page-budget';\n\t\tcase 'scope-too-large':\n\t\t\treturn 'narrow-scope';\n\t\tcase 'interrupted':\n\t\tcase 'exhausted':\n\t\t\treturn 'none';\n\t\tdefault:\n\t\t\tcause satisfies never;\n\t\t\treturn 'none';\n\t}\n}\n\n/**\n * Builds the warning for a read that returned less than everything, carrying the two facts a consumer cannot\n * recover on its own: did the request succeed, and would anything fetch the rest?\n *\n * Every incompleteness warning the facade raises on its own terms goes through here, so no read can drift\n * into claiming success on a failure or offering a \"load more\" that cannot deliver. The omission it attaches\n * is the same shape `assessCollectionMetadata` derives from SDK metadata, so a consumer never has to care\n * which layer noticed the gap.\n *\n * No `scope`: this applies to the whole read, not to one repository or project the way an SDK-attributed\n * omission can, so a consumer acting on `recovery` re-runs the read itself.\n */\nexport function incompleteReadWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tmessage: string,\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\tconst warning = otherWarning(id, domain, connectionId, message);\n\tif (cause === 'interrupted') return warning;\n\n\treturn {\n\t\t...warning,\n\t\tomission: {\n\t\t\tkind: 'pagination-incomplete',\n\t\t\trecovery: recoveryForIncompleteReadCause(cause),\n\t\t},\n\t};\n}\n\n/**\n * The read surfaces {@link truncationWarning} phrases for. `'Account-wide issue search'` names the composite\n * read that spans several provider searches rather than one surface.\n *\n * Deliberately not exported: every caller passes a literal, and publishing it would invite a consumer to switch\n * on a set that exists only to word a message.\n */\ntype TruncatedReadKind =\n\t| 'Pull request'\n\t| 'Issue'\n\t| 'Repository'\n\t| 'Organization'\n\t| 'Project'\n\t| 'Account-wide issue search'\n\t| 'Issue search'\n\t| 'Pull request search';\n\n/** {@link incompleteReadWarning} for a paged or drained read, phrased per surface. */\nexport function truncationWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n\t/**\n\t * Deliberately REQUIRED and not defaulted: a default would make one of these the silent fallback at any\n\t * call site that forgot it, and both of the claims it carries — \"the request succeeded\" and \"more can be\n\t * fetched\" — are ones this warning must never make by accident.\n\t */\n\tcause: IncompleteReadCause,\n): ProviderWarning {\n\treturn incompleteReadWarning(id, domain, connectionId, truncationMessage(id, readKind, cause), cause);\n}\n\n/**\n * The part of a drain that paging could not reach, when that drain ALSO stopped at its page budget with a usable\n * cursor — one facet of a composite read reaching the provider's ceiling while a sibling can still page. Reported\n * beside the `page-budget` {@link truncationWarning}, never in place of it: raising the budget returns more of the\n * rest, and nothing returns this part, so each warning speaks only for its own.\n *\n * The message names no mechanism. The fact behind it is `paging.truncated`, which a provider cap sets but so does\n * `hasMore` with no usable cursor (GitHub's `structuralIncompleteness`) and any per-repository truncation on the\n * repo-scoped path, so \"capped by the provider\" would name a cause we cannot confirm.\n */\nexport function cappedBesideBudgetWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: TruncatedReadKind,\n): ProviderWarning {\n\treturn incompleteReadWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`${readKind} read for '${id}' could not reach part of its results by paging; raising the page budget will not return those.`,\n\t\t'exhausted',\n\t);\n}\n\nfunction truncationMessage(id: IntegrationIds, readKind: TruncatedReadKind, cause: IncompleteReadCause): string {\n\tswitch (cause) {\n\t\tcase 'interrupted':\n\t\t\t// Deliberately does not name a mechanism. This fires both when a page was lost mid-drain and when a\n\t\t\t// drain that latched an earlier scope failure later stopped for its own reasons; \"was interrupted\"\n\t\t\t// would be false in the second. What is true in both is that something failed and results are gone.\n\t\t\treturn `${readKind} read for '${id}' did not complete; some results are missing and the read reported a failure.`;\n\t\tcase 'page-budget':\n\t\t\treturn `${readKind} read for '${id}' stopped at its page budget; more results can be read by raising it.`;\n\t\tcase 'exhausted':\n\t\t\treturn `${readKind} read for '${id}' was truncated and cannot be continued; results may be incomplete.`;\n\t\tcase 'scope-too-large':\n\t\t\t// Says what is true — the scope is larger than a read of it — rather than blaming the provider for a\n\t\t\t// ceiling that is ours, and names the remedy that exists, since no retry or budget has one.\n\t\t\treturn `${readKind} read for '${id}' covers more than one read can return; narrow the scope to read the rest.`;\n\t}\n\t// No `default`: `IncompleteReadCause` is declared in this file, so `noImplicitReturns` already fails the\n\t// build here if a cause is added without its own wording. (`collectionOmissionMessage`'s `satisfies never`\n\t// is not the same case — that union is the SDK's, and can widen under a dependency bump.)\n}\n\n/**\n * The one sentence every \"you asked for a narrowing this provider can't express\" refusal uses.\n *\n * Shared because the three callers below differ only in a noun: the wording is consumer-visible, and three\n * hand-written copies drift three ways — including the `(supported: …)` clause, which exists to avoid emitting an\n * empty parenthetical and has to be the same guard in all of them.\n */\nfunction unsupportedNarrowingMessage(id: IntegrationIds, noun: string, requested: string, supported: string): string {\n\treturn `The requested ${noun} (${requested}) are not supported by '${id}'${supported.length > 0 ? ` (supported: ${supported})` : ''}; skipped to avoid returning a wider result than requested.`;\n}\n\n/** Warning for an account-wide issue read whose requested filters the provider can't express server-side. */\nexport function unsupportedAccountWideIssueFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: IssueFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWideIssueFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide issue filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for an account-wide PR read whose requested relationship union cannot be expressed exactly. */\nexport function unsupportedAccountWidePullRequestFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tfilters: PullRequestFilter[],\n): ProviderWarning {\n\tconst supported = providersMetadata[id]?.supportedAccountWidePullRequestFilters ?? [];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'account-wide pull request filters', filters.join(', '), supported.join(', ')),\n\t);\n}\n\n/** Warning for a repo-scoped PR read whose requested filters the provider supports none of. */\nexport function unsupportedFiltersWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`The requested pull request filters are not supported by '${id}'; skipped to avoid returning unfiltered results.`,\n\t);\n}\n\n/**\n * The one sentence every `unusable-scope` refusal uses — the five surfaces that validate a search scope (both\n * searches, both count probes, and issue broadening) differ only in the noun and in whether they name a count\n * key, so the reason a scope is unusable is written once. `prefix` is what the count probes put in front of it\n * to name their key.\n *\n * NAMES the offending values, because which failure mode applied is not deducible from the value: a quote makes\n * `git\"kraken` a request for the real and different org `gitkraken`, a space makes `my org` a search of `my`\n * filtered by the text `org`, and a value of only quotes or whitespace leaves no scope at all. The caller is the\n * only layer that knows which scope it meant.\n *\n * Says how to fix it rather than what was stripped: a consumer cannot mirror this rule (unlike free-text\n * sanitizing, whose rules are published for exactly that), so \"pass the scope as the provider names it\" is the\n * actionable half.\n */\nexport function unusableSearchScopeMessage(noun: string, scopes: readonly string[], prefix?: string): string {\n\treturn `${prefix != null ? `${prefix}: ` : ''}The requested ${noun} (${scopes.map(s => JSON.stringify(s)).join(', ')}) cannot be used as given: a scope name cannot contain quotes, or an inner space or control character, which a query would drop or read as a second qualifier — searching a different scope, or none. Pass the scope exactly as the provider names it.`;\n}\n\n/** Warning for a filtered pull-request search the provider cannot run as requested. */\nexport function unsupportedPullRequestSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: PullRequestSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered pull request search is not supported by '${id}'; use \\`listPullRequestsPage\\` for its own pull request reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedPullRequestSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'pull request search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describePullRequestSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\nfunction describePullRequestSearchCapabilities(capabilities: PullRequestSearchCapabilities): string {\n\treturn [\n\t\t...(capabilities.relationships.length ? [`relationships:${capabilities.relationships.join('|')}`] : []),\n\t\t...(capabilities.states.length ? [`states:${capabilities.states.join('|')}`] : []),\n\t\t...(capabilities.text ? ['text'] : []),\n\t\t...(capabilities.updatedAfter ? ['updatedAfter'] : []),\n\t\t...(capabilities.createdAfter ? ['createdAfter'] : []),\n\t\t...(capabilities.includeArchived ? ['includeArchived'] : []),\n\t\t...(capabilities.draft ? ['draft'] : []),\n\t\t...(capabilities.repositoryScope ? ['repository scope'] : []),\n\t\t...(capabilities.organizationScope ? ['organization scope'] : []),\n\t\t...(capabilities.sorts.length ? [`sorts:${capabilities.sorts.join('|')}`] : []),\n\t].join(', ');\n}\n\n/**\n * Warning for a filtered issue search whose criteria the provider can't serve — either it has no such search at\n * all, or it can't express what was asked for, or the set contradicts itself.\n *\n * One builder for all three because they are one decision from the caller's side (\"this search can't be run as\n * asked\") and the remedy differs only in what the message says. The rejection shape carries which case it is, so\n * the wording can't drift from the check that produced it (see {@link resolveIssueSearchCriteria}). The kind\n * follows the reason: the first two are `'unsupported'` capabilities; a contradictory set is caller input and\n * stays `'other'`.\n */\nexport function unsupportedIssueSearchCriteriaWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: IssueSearchCriteriaRejection,\n): ProviderWarning {\n\tlet message: string;\n\tswitch (rejection.reason) {\n\t\tcase 'unsupported-search':\n\t\t\tmessage = `Filtered issue search is not supported by '${id}'; use \\`listIssuesPage\\` for its own issue reads.`;\n\t\t\tbreak;\n\t\tcase 'unsupported-criteria': {\n\t\t\tconst supported = providersMetadata[id]?.supportedIssueSearch;\n\t\t\tmessage = unsupportedNarrowingMessage(\n\t\t\t\tid,\n\t\t\t\t'issue search criteria',\n\t\t\t\trejection.criteria.join(', '),\n\t\t\t\tsupported != null ? describeIssueSearchCapabilities(supported) : '',\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'contradictory-relationships':\n\t\t\t// Caller input the caller fixes in the request itself, so `'other'` rather than `'unsupported'`.\n\t\t\treturn otherWarning(\n\t\t\t\tid,\n\t\t\t\tdomain,\n\t\t\t\tconnectionId,\n\t\t\t\t`The relationships \\`any-assignee\\` and \\`unassigned\\` are contradictory — they partition the scope between them, so no issue satisfies both; pass only one.`,\n\t\t\t);\n\t}\n\t// No `default`: the union is declared alongside the validator, so `noImplicitReturns` fails the build here if\n\t// a rejection reason is added without its own wording.\n\n\treturn unsupportedWarning(id, domain, connectionId, message);\n}\n\n/** The criteria a provider CAN express, for the \"supported: …\" half of a refusal message. */\nfunction describeIssueSearchCapabilities(capabilities: IssueSearchCapabilities): string {\n\tconst flags: [name: string, supported: boolean][] = [\n\t\t['text', capabilities.text],\n\t\t['labels', capabilities.labels],\n\t\t['milestone', capabilities.milestone],\n\t\t['updatedAfter', capabilities.updatedAfter],\n\t\t['createdAfter', capabilities.createdAfter],\n\t\t['withoutLinkedPullRequest', capabilities.withoutLinkedPullRequest],\n\t\t['state', capabilities.states],\n\t];\n\treturn [\n\t\t...capabilities.relationships.map(r => `relationships:${r}`),\n\t\t...flags.filter(([, supported]) => supported).map(([name]) => name),\n\t\t...capabilities.sorts.map(s => `sort:${s}`),\n\t].join(', ');\n}\n\n/**\n * Warning for an issue read asked to order by a key its provider can't express server-side.\n *\n * Separate from {@link unsupportedIssueSearchCriteriaWarning} because it serves the reads that take `sort` as an\n * OPTION rather than as a criterion — repo-scoped, account-wide, and issue-tracker — where there is no criteria\n * set to fold it into. The filtered search keeps reporting it as one more `unsupported-criteria` entry, so a\n * caller there still gets a single refusal listing everything at once.\n *\n * Takes the rejection the check produced rather than a re-read supported list: which of the two tables applies is\n * the caller's own branch (`repos` present or not), so reading it back here — or threading it by hand — would be\n * free to disagree with the check that refused.\n */\nexport function unsupportedIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\trejection: UnsupportedIssueSortRejection,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\tunsupportedNarrowingMessage(id, 'issue sort', rejection.requested, (rejection.supported ?? []).join(', ')),\n\t);\n}\n\n/**\n * Warning for a merged issue read asked to order by a key that no normalized issue carries.\n *\n * The distinction this exists to make is not about the provider: the provider orders by `priority` perfectly well\n * within ONE project. It is about the read, which fans out over several and merges the results here, where the\n * only fields available are the ones {@link IssueShape} models. Serving the concatenated per-scope runs would look\n * ordered without being so, which is why this refuses instead — and why the message says WHY, since the same key\n * against the same provider succeeds on a single-scope read.\n *\n * What a read fans out OVER differs by read, hence the `scope`: repositories, projects, or — where one query is\n * issued per requested relationship and the runs are unioned within a single project (Jira) — the filters\n * themselves. Naming the wrong one would tell a caller to narrow something that isn't what merged.\n */\nexport function unmergeableIssueSortWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\tunmergeable: UnmergeableIssueSort,\n\tscope: 'repositories' | 'projects' | 'filters',\n): ProviderWarning {\n\t// Both forms are spelled out rather than depluralized at runtime: the message needs each, and\n\t// `'repositories'.replace(/s$/, '')` is `'repositorie'`.\n\tconst nouns = {\n\t\trepositories: { many: 'repositories', one: 'repository' },\n\t\tprojects: { many: 'projects', one: 'project' },\n\t\tfilters: { many: 'issue relationships', one: 'relationship' },\n\t}[scope];\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Cannot order by '${unmergeable.requested}' across several ${nouns.many}: this read merges their results, and a normalized issue carries no such field to merge on. Read one ${nouns.one} at a time, or order by a date, title, comment or reaction count.`,\n\t);\n}\n\n/**\n * The omission for a filtered issue search that hit the provider's RESULT CEILING: more issues matched than the\n * provider will ever serve for one query, however it is paged.\n *\n * Distinct from {@link truncationWarning} because this is the one incompleteness this read can quantify —\n * `totalCount` is how many matched, `limit` how many are reachable — which is what lets a consumer say \"19.240\n * matched, showing the 1.000 most recently updated\" instead of a bare \"results may be incomplete\". `recovery:\n * 'none'` because the items past the ceiling are UNREACHABLE, not merely unfetched: no budget, no retry, and no\n * cursor would return them. Narrowing the criteria is the only way through, which is a decision for the caller.\n *\n * The message names the ORDER, and has to now that ordering is an option: the reachable window is the top `limit`\n * under that key, so \"the 1.000 most recent\" is a true sentence only while the key is the default one. The same\n * value goes on the omission, so a consumer wording its own sentence doesn't have to remember what it asked for.\n *\n * Returns `undefined` when the provider declares no ceiling, so the caller falls back to the generic wording\n * rather than reporting a limit it invented.\n */\nexport function issueSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting,\n\t/** The provider proved the ceiling was reached without reporting how many matched; see `limitReached`. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Issue search',\n\t\tprovidersMetadata[id]?.issueSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/**\n * The shared body of the two ceiling warnings. Only a ceiling that was actually reached warns: either a reported\n * total above it, or a provider that proved the ceiling was reached but can't count past it — in which case the\n * omission carries the limit and the order but no `totalCount`, since any figure would be invented.\n */\nfunction searchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\treadKind: 'Issue search' | 'Pull request search',\n\tlimit: number | undefined,\n\ttotalCount: number | undefined,\n\tsort: IssueSorting | PullRequestSorting,\n\tlimitReached: boolean | undefined,\n): ProviderWarning | undefined {\n\tif (limit == null) return undefined;\n\n\t// Below the ceiling this warning would be a false claim; the caller's truncation had another cause.\n\tconst counted = totalCount != null && totalCount > limit;\n\tif (!counted && limitReached !== true) return undefined;\n\n\tconst [field, direction] = sort.split(':');\n\tconst matched = counted ? `matched ${totalCount} results` : `matched more than ${limit} results`;\n\treturn {\n\t\t...otherWarning(\n\t\t\tid,\n\t\t\tdomain,\n\t\t\tconnectionId,\n\t\t\t`${readKind} ${matched}, but '${id}' serves at most ${limit}, ordered by ${field} ${direction}ending; narrow the search to read the rest.`,\n\t\t),\n\t\tomission: {\n\t\t\tkind: 'provider-limit',\n\t\t\t// The items past the ceiling can't be fetched by anything, so never offer a \"load more\".\n\t\t\trecovery: 'none',\n\t\t\tlimit: limit,\n\t\t\t...(counted ? { totalCount: totalCount } : {}),\n\t\t\tsort: sort,\n\t\t},\n\t};\n}\n\n/** Quantified omission for a pull-request search that exceeded the provider's reachable result window. */\nexport function pullRequestSearchCapResultWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n\ttotalCount: number | undefined,\n\tsort: PullRequestSorting,\n\t/** See {@link issueSearchCapResultWarning}. */\n\tlimitReached?: boolean,\n): ProviderWarning | undefined {\n\t// Named order for the same reason as the issue twin: the reachable window is the first `limit` under THIS sort.\n\treturn searchCapResultWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t'Pull request search',\n\t\tprovidersMetadata[id]?.pullRequestSearchResultLimit,\n\t\ttotalCount,\n\t\tsort,\n\t\tlimitReached,\n\t);\n}\n\n/** Warning for a git host that doesn't expose issues on this surface (e.g. Bitbucket, deprecated in favor of Jira). */\nexport function issuesUnsupportedWarning(\n\tid: IntegrationIds,\n\tdomain: string | undefined,\n\tconnectionId: string | undefined,\n): ProviderWarning {\n\treturn unsupportedWarning(\n\t\tid,\n\t\tdomain,\n\t\tconnectionId,\n\t\t`Issues are not supported by '${id}'; use a dedicated issue integration (e.g. Jira) instead.`,\n\t);\n}\n"],"mappings":"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"}
|
|
@@ -10,11 +10,19 @@ export interface ConnectionStateChangeEvent {
|
|
|
10
10
|
* A per-provider, per-connection warning surfaced alongside (partial) read results. Consumers use
|
|
11
11
|
* these to drive auth recovery, retry, or truncation messaging without the read itself throwing.
|
|
12
12
|
*
|
|
13
|
-
* `kind` is the programmatic discriminant (a rate-limit is retryable, a 404 is not, an
|
|
14
|
-
* needs re-connection unless {@link ProviderWarning.scope} confines
|
|
15
|
-
* convenience mirror of `kind === 'auth'`.
|
|
13
|
+
* `kind` is the programmatic discriminant (a rate-limit is retryable, a 404 is not, an unsupported capability
|
|
14
|
+
* never succeeds on retry, an auth failure needs re-connection unless {@link ProviderWarning.scope} confines
|
|
15
|
+
* it); `isAuth` is retained as a convenience mirror of `kind === 'auth'`.
|
|
16
|
+
*
|
|
17
|
+
* `'unsupported'`: the provider — or this integration of it — lacks the capability the request asked for: a
|
|
18
|
+
* surface (issues on Bitbucket, pull requests on an issue tracker, a batch read, org/repo discovery,
|
|
19
|
+
* current-account lookup, project-scoped or account-wide issue reads), or a filter, sort, state, search
|
|
20
|
+
* criterion or option combination it cannot express. Retrying the same request never succeeds; a different
|
|
21
|
+
* provider, scope or request may. NOT unsupported (these stay `'other'`): malformed or contradictory caller
|
|
22
|
+
* input the caller fixes in the request itself (a duplicate batch key, invalid target numbers, an empty owner,
|
|
23
|
+
* an unusable scope), incompleteness or omission warnings, and unclassified errors.
|
|
16
24
|
*/
|
|
17
|
-
export type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'other';
|
|
25
|
+
export type ProviderWarningKind = 'auth' | 'rate-limit' | 'not-found' | 'no-connection' | 'unsupported' | 'other';
|
|
18
26
|
/**
|
|
19
27
|
* Why a read that SUCCEEDED still withheld results:
|
|
20
28
|
* - `provider-limit`: the provider refuses to serve past a cap it enforces (GitHub search's 1,000-result
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from "../../git/errors.js";
|
|
2
|
+
import { isUnsupportedSortError } from "@gitkraken/provider-apis";
|
|
2
3
|
//#region ../plus/integrations/src/results.ts
|
|
3
4
|
const maxProviderWarningMessageLength = 500;
|
|
4
5
|
/** A caught error's message as a warning can carry it: an HTML page becomes its status, and prose is capped. */
|
|
@@ -20,6 +21,7 @@ function toProviderWarning(providerId, domain, connectionId, ex) {
|
|
|
20
21
|
if (ex instanceof AuthenticationError) kind = "auth";
|
|
21
22
|
else if (ex instanceof RequestRateLimitError) kind = "rate-limit";
|
|
22
23
|
else if (ex instanceof RequestNotFoundError) kind = "not-found";
|
|
24
|
+
else if (isUnsupportedSortError(ex)) kind = "unsupported";
|
|
23
25
|
else kind = "other";
|
|
24
26
|
return {
|
|
25
27
|
providerId,
|