@gitkraken/core-gitlens 0.11.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -1
- package/dist/git/cache.d.ts +84 -29
- package/dist/git/cache.js +188 -54
- package/dist/git/cache.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +4 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/branch.d.ts +15 -2
- package/dist/git/models/branch.js +27 -5
- package/dist/git/models/branch.js.map +1 -1
- package/dist/git/models/issue.d.ts +3 -1
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/models/status.js +1 -1
- package/dist/git/models/worktree.d.ts +4 -1
- package/dist/git/models/worktree.js +4 -2
- package/dist/git/models/worktree.js.map +1 -1
- package/dist/git/providers/branches.d.ts +73 -8
- package/dist/git/providers/pausedOperations.d.ts +6 -0
- package/dist/git/providers/provider.d.ts +34 -2
- package/dist/git/providers/status.d.ts +8 -2
- package/dist/git/remotes/azure-devops.js +61 -53
- package/dist/git/remotes/azure-devops.js.map +1 -1
- package/dist/git/service.d.ts +8 -5
- package/dist/git/service.js +16 -8
- package/dist/git/service.js.map +1 -1
- package/dist/git/utils/branch.utils.d.ts +8 -1
- package/dist/git/utils/branch.utils.js +30 -1
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/utils/issue.utils.d.ts +3 -1
- package/dist/git/utils/issue.utils.js +5 -1
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +13 -2
- package/dist/git/watching/changeEvent.js +17 -3
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/gitDirPoller.d.ts +111 -0
- package/dist/git/watching/gitDirPoller.js +266 -0
- package/dist/git/watching/gitDirPoller.js.map +1 -0
- package/dist/git/watching/provider.d.ts +16 -0
- package/dist/git/watching/watchGroup.d.ts +40 -4
- package/dist/git/watching/watchGroup.js +143 -17
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +70 -1
- package/dist/git/watching/watchService.js +158 -13
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +44 -0
- package/dist/git/watching/watchSession.js +91 -6
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +21 -0
- package/dist/git/watching/watcherPatterns.js +67 -1
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +34 -7
- package/dist/git/watching/worktreeSummary.js +39 -12
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +34 -2
- package/dist/git-cli/cliGitProvider.js +119 -17
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/commandClassifier.d.ts +15 -0
- package/dist/git-cli/exec/commandClassifier.js +48 -2
- package/dist/git-cli/exec/commandClassifier.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +15 -3
- package/dist/git-cli/exec/git.js +38 -17
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +2 -0
- package/dist/git-cli/exec/gitQueue.js +2 -1
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/exec/indexLock.d.ts +38 -0
- package/dist/git-cli/exec/indexLock.js +150 -0
- package/dist/git-cli/exec/indexLock.js.map +1 -0
- package/dist/git-cli/parsers/worktreeParser.js +42 -2
- package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +109 -10
- package/dist/git-cli/providers/branches.js +624 -131
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +9 -5
- package/dist/git-cli/providers/config.js +60 -27
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +17 -0
- package/dist/git-cli/providers/operations.js +153 -96
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +8 -0
- package/dist/git-cli/providers/pausedOperations.js +60 -40
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.js +11 -8
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/remotes.js +19 -10
- package/dist/git-cli/providers/remotes.js.map +1 -1
- package/dist/git-cli/providers/staging.js +40 -15
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +2 -0
- package/dist/git-cli/providers/stash.js +33 -18
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +3 -1
- package/dist/git-cli/providers/status.js +38 -20
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/tags.js +6 -4
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +1 -0
- package/dist/git-cli/providers/worktrees.js +60 -31
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts +6 -0
- package/dist/git-cli/service.js +5 -2
- package/dist/git-cli/service.js.map +1 -1
- package/dist/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
- package/dist/plus/git-github/providers/github/status.d.ts +3 -1
- package/dist/plus/git-github/providers/github/status.js +1 -1
- package/dist/plus/git-github/providers/github/status.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +13 -2
- package/dist/plus/integrations/collectionMetadata.js +14 -2
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +1 -0
- package/dist/plus/integrations/integrationService.js +1 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +41 -5
- package/dist/plus/integrations/models/gitHostIntegration.js +109 -30
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.js +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +17 -4
- package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
- package/dist/plus/integrations/providers/apiClients.js +7 -1
- package/dist/plus/integrations/providers/apiClients.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +5 -4
- package/dist/plus/integrations/providers/azure/azure.js +38 -77
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.utils.js +20 -17
- package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +27 -10
- package/dist/plus/integrations/providers/azure/models.js +40 -20
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +23 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +84 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.d.ts +44 -0
- package/dist/plus/integrations/providers/azureDevOps.js +113 -22
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +45 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +121 -6
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
- package/dist/plus/integrations/providers/jira-server.js +29 -19
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +5 -2
- package/dist/plus/integrations/providers/jira.js +63 -44
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +3 -4
- package/dist/plus/integrations/providers/jiraIssueByKey.js +3 -4
- package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
- package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +5 -5
- package/dist/plus/integrations/providers/linearIssuesEtag.js +6 -6
- package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +12 -3
- package/dist/plus/integrations/providers/models.js +16 -9
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +21 -33
- package/dist/plus/integrations/providers/providersApi.js +159 -49
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +49 -15
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +132 -10
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +8 -2
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/cursors.js +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +0 -1
- package/dist/plus/integrations/reads/filters.d.ts +3 -3
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +2 -2
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.utils.d.ts +13 -0
- package/dist/plus/integrations/reads/hierarchy.utils.js +26 -1
- package/dist/plus/integrations/reads/hierarchy.utils.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +2 -1
- package/dist/plus/integrations/reads/issueBatch.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +27 -18
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -1
- package/dist/plus/integrations/reads/issues.js +19 -4
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.js +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +12 -3
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +11 -4
- package/dist/plus/integrations/reads/warnings.js +15 -5
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +48 -4
- package/dist/plus/integrations/results.js +33 -13
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/plus/integrations/utils/integration.utils.d.ts +6 -1
- package/dist/plus/integrations/utils/integration.utils.js +8 -1
- package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
- package/dist/utils/env/node/exec.js +2 -2
- package/dist/utils/promiseCache.d.ts +10 -0
- package/dist/utils/promiseCache.js +17 -2
- package/dist/utils/promiseCache.js.map +1 -1
- package/docs/integrations.md +93 -31
- package/package.json +2 -2
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Logger as _logger } from "../../../utils/logger.js";
|
|
2
2
|
import "../constants.js";
|
|
3
3
|
import { isJiraMissingProjectError } from "./providerErrors.js";
|
|
4
|
-
import { toCollectionScopeFailure } from "../collectionMetadata.js";
|
|
4
|
+
import { isIncompleteCollection, toCollectionScopeFailure } from "../collectionMetadata.js";
|
|
5
5
|
import { toTokenWithInfo } from "../authentication/models.js";
|
|
6
|
-
import { mergeCollectionMetadata } from "./utils/providerPaging.js";
|
|
7
6
|
import { areDomainsOnSameHost, baseUrlFromDomain } from "../utils/domain.utils.js";
|
|
7
|
+
import { mergeCollectionMetadata } from "./utils/providerPaging.js";
|
|
8
8
|
import { providersMetadata, toAccount, toIssueShape, toProviderIssueStates } from "./models.js";
|
|
9
9
|
import { IssuesIntegration, groupProjectIssuesSearches } from "../models/issuesIntegration.js";
|
|
10
10
|
import { DiscoveryCache } from "./utils/discoveryCache.js";
|
|
11
|
-
import { drainJiraIssues, readUserScopedJiraIssues, searchJiraProjectIssues, toUniqueJiraIssueShapes } from "./utils/jiraIssueDrains.js";
|
|
11
|
+
import { drainJiraIssues, readUserScopedJiraIssues, searchJiraProjectIssues, summarizePageMetadata, toUniqueJiraIssueShapes, withFailureScope } from "./utils/jiraIssueDrains.js";
|
|
12
12
|
import { JIRA_MAX_PROJECT_KEYS_PER_REQUEST } from "@gitkraken/provider-apis";
|
|
13
13
|
//#region ../plus/integrations/src/providers/jira-server.ts
|
|
14
14
|
const metadata = providersMetadata["jira-server"];
|
|
@@ -107,7 +107,7 @@ var JiraServerIntegration = class extends IssuesIntegration {
|
|
|
107
107
|
const resourceId = this.resourceFor(session).id;
|
|
108
108
|
const descriptors = projects.map((p) => ({
|
|
109
109
|
id: p.id,
|
|
110
|
-
key: p.
|
|
110
|
+
key: p.key?.trim() || p.id,
|
|
111
111
|
name: p.name,
|
|
112
112
|
resourceId
|
|
113
113
|
}));
|
|
@@ -145,29 +145,33 @@ var JiraServerIntegration = class extends IssuesIntegration {
|
|
|
145
145
|
};
|
|
146
146
|
if (options?.user != null) {
|
|
147
147
|
const read = await readUserScopedJiraIssues(fetchPage, options.filters, toUserIdentities(options.user), drainOptions);
|
|
148
|
-
const
|
|
148
|
+
const drain = {
|
|
149
|
+
values: toUniqueJiraIssueShapes(read.issues, this),
|
|
150
|
+
metadata: read.metadata,
|
|
151
|
+
scope: drainOptions.failureScope
|
|
152
|
+
};
|
|
149
153
|
return read.truncated ? {
|
|
150
|
-
|
|
154
|
+
...drain,
|
|
151
155
|
truncated: true,
|
|
152
|
-
recovery: "none"
|
|
153
|
-
metadata: read.metadata
|
|
156
|
+
recovery: "none"
|
|
154
157
|
} : {
|
|
155
|
-
|
|
156
|
-
truncated: false
|
|
157
|
-
metadata: read.metadata
|
|
158
|
+
...drain,
|
|
159
|
+
truncated: false
|
|
158
160
|
};
|
|
159
161
|
}
|
|
160
162
|
const unscoped = await drainJiraIssues(fetchPage, {}, drainOptions);
|
|
161
|
-
const
|
|
163
|
+
const drain = {
|
|
164
|
+
values: unscoped.issues.map((issue) => toIssueShape(issue, this, { projection: "project" })).filter((result) => result !== void 0),
|
|
165
|
+
metadata: unscoped.metadata,
|
|
166
|
+
scope: drainOptions.failureScope
|
|
167
|
+
};
|
|
162
168
|
return unscoped.status !== "complete" ? {
|
|
163
|
-
|
|
169
|
+
...drain,
|
|
164
170
|
truncated: true,
|
|
165
|
-
recovery: unscoped.status === "backstop" ? "narrow-scope" : "none"
|
|
166
|
-
metadata: unscoped.metadata
|
|
171
|
+
recovery: unscoped.status === "backstop" ? "narrow-scope" : "none"
|
|
167
172
|
} : {
|
|
168
|
-
|
|
169
|
-
truncated: false
|
|
170
|
-
metadata: unscoped.metadata
|
|
173
|
+
...drain,
|
|
174
|
+
truncated: false
|
|
171
175
|
};
|
|
172
176
|
}
|
|
173
177
|
/**
|
|
@@ -186,6 +190,7 @@ var JiraServerIntegration = class extends IssuesIntegration {
|
|
|
186
190
|
async searchProviderProjectIssues(session, requests) {
|
|
187
191
|
const { options } = requests[0];
|
|
188
192
|
if (options.user == null) return void 0;
|
|
193
|
+
if (requests.length === 1) return void 0;
|
|
189
194
|
const tokenWithInfo = toTokenWithInfo(this.id, session);
|
|
190
195
|
const api = await this.getProvidersApi();
|
|
191
196
|
const baseUrl = this.apiBaseUrlFor(session);
|
|
@@ -224,6 +229,7 @@ var JiraServerIntegration = class extends IssuesIntegration {
|
|
|
224
229
|
let cursor;
|
|
225
230
|
let truncated = false;
|
|
226
231
|
let metadata;
|
|
232
|
+
const pages = [];
|
|
227
233
|
const stopIncomplete = (reason) => {
|
|
228
234
|
truncated = true;
|
|
229
235
|
metadata = mergeCollectionMetadata(metadata, {
|
|
@@ -242,6 +248,10 @@ var JiraServerIntegration = class extends IssuesIntegration {
|
|
|
242
248
|
break;
|
|
243
249
|
}
|
|
244
250
|
results.push(...page.data.map((issue) => toIssueShape(issue, this, { projection: "account" })).filter((r) => r != null));
|
|
251
|
+
if (page.metadata != null) {
|
|
252
|
+
truncated ||= isIncompleteCollection(page.metadata);
|
|
253
|
+
pages.push(withFailureScope(page.metadata, accountScope));
|
|
254
|
+
}
|
|
245
255
|
if (!page.hasMore) break;
|
|
246
256
|
if (page.nextCursor == null || page.nextCursor === cursor) {
|
|
247
257
|
stopIncomplete("Jira Server returned no advancing issue continuation");
|
|
@@ -253,7 +263,7 @@ var JiraServerIntegration = class extends IssuesIntegration {
|
|
|
253
263
|
return {
|
|
254
264
|
values: results,
|
|
255
265
|
truncated,
|
|
256
|
-
metadata
|
|
266
|
+
metadata: mergeCollectionMetadata(summarizePageMetadata(pages), metadata)
|
|
257
267
|
};
|
|
258
268
|
}
|
|
259
269
|
async getProviderLinkedIssueOrPullRequest(session, _resource, { key }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jira-server.js","names":[],"sources":["../../../../../plus/integrations/src/providers/jira-server.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport { JIRA_MAX_PROJECT_KEYS_PER_REQUEST } from '@gitkraken/provider-apis';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { Issue, IssueProjection, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { Emitter } from '@gitlens/utils/event.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { IssuesSelfManagedHostIntegrationId } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport type { IntegrationKey } from '../models/integration.js';\nimport type {\n\tAccountWideIssuesResult,\n\tIssuesForProjectOptions,\n\tProjectIssuesDrain,\n\tProjectIssuesRequest,\n\tSearchMyIssuesOptions,\n} from '../models/issueReads.js';\nimport { groupProjectIssuesSearches, IssuesIntegration } from '../models/issuesIntegration.js';\nimport { areDomainsOnSameHost, baseUrlFromDomain } from '../utils/domain.utils.js';\nimport { providersMetadata, toAccount, toIssueShape, toProviderIssueStates } from './models.js';\nimport { isJiraMissingProjectError } from './providerErrors.js';\nimport type { ProvidersApi } from './providersApi.js';\nimport { DiscoveryCache } from './utils/discoveryCache.js';\nimport type { JiraDrainOptions, JiraIssuePageFetcher } from './utils/jiraIssueDrains.js';\nimport {\n\tdrainJiraIssues,\n\treadUserScopedJiraIssues,\n\tsearchJiraProjectIssues,\n\ttoUniqueJiraIssueShapes,\n} from './utils/jiraIssueDrains.js';\nimport { mergeCollectionMetadata } from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[IssuesSelfManagedHostIntegrationId.JiraServer];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n\n/**\n * Every clause is scoped by the HANDLE, and `options.userId` is deliberately ignored here — the one\n * place this provider diverges from Jira Cloud's, which applies it to `assignee`/`creator` (#5857).\n *\n * The two are not interchangeable because `Account.id` means something different per deployment. On\n * Cloud it is the `accountId`, which JQL resolves, so scoping by it fixes the deactivated-account case\n * where the display name no longer resolves. On Server it is the user KEY (`JIRAUSER10224`), which JQL\n * does not resolve at all: measured against a live Jira Server 8.20.13 site, `assignee in\n * (\"JIRAUSER10224\")` returns 0 with `\"The value 'JIRAUSER10224' does not exist for the field\n * 'assignee'\"` — the identical warning a genuinely nonexistent user produces — while the username and\n * the display name both return the real 4.\n *\n * So applying `userId` here would silently empty the read rather than harden it. Do not \"fix\" this\n * asymmetry to match Cloud without re-measuring against a Server instance.\n *\n * The handle itself is already the right one: `provider-apis` 0.60.0 maps `Account.username` from the\n * Server payload's `name` (falling back to `displayName` when absent), so these clauses are scoped by the\n * real username rather than a display name (GKDEV-3629). That is what makes the `mention` clause work at\n * all — it is a free-text `comment ~ \"...\"` over comment bodies, and a Jira mention is stored as\n * `[~username]`, which a display name never matches.\n */\nfunction toUserIdentities(user: string): { userField: string; mention: string } {\n\treturn { userField: user, mention: user };\n}\n\n/**\n * The single resource a Jira Server connection has: the instance itself.\n *\n * Jira Cloud's account spans many sites, so its resources come from an API call that lists them. A\n * self-hosted instance IS the one site the connection addresses, so there is nothing to enumerate — this\n * descriptor is synthesized from the configured host. Modelling it as a resource anyway keeps the\n * resource → project contract of {@link IssuesIntegration} intact, so every tracker read (`listOrgs`,\n * `listProjects`, `listIssueTrackerIssuesPage`) works unchanged.\n */\nexport interface JiraServerResourceDescriptor extends IssueResourceDescriptor {\n\turl: string;\n}\n\nexport interface JiraServerProjectDescriptor extends IssueResourceDescriptor {\n\tresourceId: string;\n}\n\nexport class JiraServerIntegration extends IssuesIntegration<IssuesSelfManagedHostIntegrationId.JiraServer> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\treadonly id = IssuesSelfManagedHostIntegrationId.JiraServer;\n\tprotected readonly key: IntegrationKey<IssuesSelfManagedHostIntegrationId.JiraServer>;\n\treadonly name: string = 'Jira Data Center';\n\n\tconstructor(\n\t\tctx: IntegrationServiceContext,\n\t\tauthenticationService: IntegrationAuthenticationService,\n\t\tgetProvidersApi: () => Promise<ProvidersApi>,\n\t\tdidChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t\tprivate readonly _domain: string,\n\t) {\n\t\tsuper(ctx, authenticationService, getProvidersApi, didChangeConnection);\n\t\tthis.key = `${this.id}:${this._domain}`;\n\t}\n\n\tget domain(): string {\n\t\treturn this._domain;\n\t}\n\n\t/**\n\t * The URL every request for this connection is addressed to, built from the SESSION's domain rather than\n\t * the integration's.\n\t *\n\t * `this.domain` is the host-keyed identity — `IntegrationService.get()` runs it through `hostFromDomain`\n\t * so one host can't build two instances — which means it has had any context path stripped, and so has\n\t * `session.domain`, which is stored as that same normalized host. Jira Data Center officially supports\n\t * being mounted below a context path (`https://jira.example.com/jira`), so the address comes from\n\t * {@link ProviderAuthenticationSession.baseUrl}, which carries the configured value verbatim; it falls\n\t * back to the host when the backend reported nothing more, which is the common case.\n\t *\n\t * Takes the session rather than reading `_session` so a per-connection (multi-account) read is addressed\n\t * to the host of the connection it resolved, not to whichever one happens to be current.\n\t */\n\tprotected apiBaseUrlFor(session: ProviderAuthenticationSession | undefined): string {\n\t\tconst configured = baseUrlFromDomain(session?.baseUrl, session?.protocol);\n\t\t// A `baseUrl` from another host is not this connection's address — refuse it rather than routing this\n\t\t// integration's reads somewhere its key never named.\n\t\tif (configured != null && areDomainsOnSameHost(session?.baseUrl, this.domain)) return configured;\n\n\t\treturn (\n\t\t\tbaseUrlFromDomain(session?.domain, session?.protocol) ??\n\t\t\tbaseUrlFromDomain(this.domain, session?.protocol) ??\n\t\t\t`${session?.protocol ?? 'https:'}//${this.domain}`\n\t\t);\n\t}\n\n\t/** The one resource this connection has — see {@link JiraServerResourceDescriptor}. */\n\tprivate resourceFor(session: ProviderAuthenticationSession | undefined): JiraServerResourceDescriptor {\n\t\treturn { id: this.domain, key: this.domain, name: this.domain, url: this.apiBaseUrlFor(session) };\n\t}\n\n\t// No `autolinks()` override, unlike Jira Cloud: an autolink prefix has to be the Jira project KEY, which\n\t// is what a commit message or branch name carries (`PROJ-123`). `/rest/api/2/project` reports no key — see\n\t// `getProviderProjectsForResources`, which falls back to the name because JQL accepts it — so the only\n\t// prefix this class could build is the display name, which matches nothing and yields a `/browse/` URL that\n\t// is not an issue. Registering that would be worse than registering none. Restore the override once the SDK\n\t// surfaces the project key (GKDEV-3629's sibling gap).\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getJiraServerCurrentUser(toTokenWithInfo(this.id, session), this.apiBaseUrlFor(session));\n\n\t\tif (user == null) return undefined;\n\t\treturn toAccount(user, this);\n\t}\n\n\tprotected override getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_force?: boolean,\n\t): Promise<JiraServerResourceDescriptor[] | undefined> {\n\t\t// Synthesized from the configured host rather than fetched, so there is nothing to cache or force.\n\t\treturn Promise.resolve([this.resourceFor(session)]);\n\t}\n\n\t/** Expires, and is dropped on a re-sync, so a project created or deleted mid-session is picked up (#5907). */\n\tprivate readonly _projects = new DiscoveryCache<JiraServerProjectDescriptor[]>();\n\tprotected override async getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraServerResourceDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<JiraServerProjectDescriptor[] | undefined> {\n\t\t// Every resource here is this instance (see `getProviderResourcesForUser`), so one read serves them\n\t\t// all; a caller that passes none is asking for nothing.\n\t\tif (resources.length === 0) return [];\n\n\t\tconst cacheKey = this.projectsCacheKey(session);\n\t\tconst cached = this._projects.get(cacheKey);\n\t\tif (cached != null && !force) return cached;\n\n\t\tconst generation = this._projects.generation;\n\t\tconst api = await this.getProvidersApi();\n\t\tconst projects = await api.getJiraServerProjects(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t);\n\t\tif (projects == null) return undefined;\n\n\t\tconst resourceId = this.resourceFor(session).id;\n\t\tconst descriptors = projects.map<JiraServerProjectDescriptor>(p => ({\n\t\t\tid: p.id,\n\t\t\t// The display name, NOT what the reads address the project by. `/rest/api/2/project` reports no\n\t\t\t// project key (GKDEV-3629's sibling gap) and the SDK's `JiraServerProject` carries only `id` and\n\t\t\t// `name`, so there is no key to put here; the reads below use `id` instead, because JQL resolves a\n\t\t\t// `project = \"<value>\"` clause against key before name, and a display name that collides with\n\t\t\t// ANOTHER project's key would silently return that project's issues.\n\t\t\tkey: p.name,\n\t\t\tname: p.name,\n\t\t\tresourceId: resourceId,\n\t\t}));\n\n\t\tthis._projects.set(cacheKey, descriptors, { generation: generation });\n\n\t\treturn descriptors;\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraServerProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraServerProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\n\t\t// The same drain as Jira Cloud's (`jiraIssueDrains.ts`): page to a defensive backstop, and report `truncated`\n\t\t// plus a structured failure rather than discarding an already-fetched prefix.\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\t// The numeric project id, not the name: JQL resolves `project = \"<value>\"` against key first, so a display\n\t\t// name that matches another project's key reads that project's issues instead. A Jira project key must begin\n\t\t// with a letter, so a purely numeric value can only be an id.\n\t\tconst fetchPage: JiraIssuePageFetcher = (scope, cursor) =>\n\t\t\tapi.getJiraServerIssuesForProjectPaged(tokenWithInfo, baseUrl, project.id, {\n\t\t\t\t...scope,\n\t\t\t\tcursor: cursor,\n\t\t\t\tstates: states,\n\t\t\t\tsort: options?.sort,\n\t\t\t});\n\t\tconst drainOptions: JiraDrainOptions = {\n\t\t\tmaxPages: maxPagesPerRequest,\n\t\t\tfailureScope: { providerId: this.id, resourceId: project.resourceId, projectId: project.id },\n\t\t\tproviderName: 'Jira Server',\n\t\t\t// A project this connection can no longer see (deleted, or its browse permission removed) holds\n\t\t\t// no issues for it, and the project list that named it is stale: drop that list so the next read\n\t\t\t// re-reads it, instead of failing the whole provider on every read until a restart (#5907).\n\t\t\tisEmptyFailure: ex => {\n\t\t\t\tif (!isJiraMissingProjectError(ex)) return false;\n\n\t\t\t\tLogger.warn(`Jira Server project '${project.name}' (${project.id}) no longer exists or is not visible`);\n\t\t\t\tthis._projects.delete(this.projectsCacheKey(session));\n\t\t\t\treturn true;\n\t\t\t},\n\t\t};\n\n\t\tif (options?.user != null) {\n\t\t\tconst read = await readUserScopedJiraIssues(\n\t\t\t\tfetchPage,\n\t\t\t\toptions.filters,\n\t\t\t\ttoUserIdentities(options.user),\n\t\t\t\tdrainOptions,\n\t\t\t);\n\t\t\tconst values = toUniqueJiraIssueShapes(read.issues, this);\n\t\t\t// `recovery: 'none'` for the whole set: the filter branches merge here, so even a pure backstop in one\n\t\t\t// branch is not fixable by narrowing this read's scope.\n\t\t\treturn read.truncated\n\t\t\t\t? { values: values, truncated: true, recovery: 'none', metadata: read.metadata }\n\t\t\t\t: { values: values, truncated: false, metadata: read.metadata };\n\t\t}\n\n\t\tconst unscoped = await drainJiraIssues(fetchPage, {}, drainOptions);\n\t\tconst values = unscoped.issues\n\t\t\t.map(issue => toIssueShape(issue, this, { projection: 'project' }))\n\t\t\t.filter((result): result is IssueShape => result !== undefined);\n\t\treturn unscoped.status !== 'complete'\n\t\t\t? {\n\t\t\t\t\tvalues: values,\n\t\t\t\t\ttruncated: true,\n\t\t\t\t\trecovery: unscoped.status === 'backstop' ? 'narrow-scope' : 'none',\n\t\t\t\t\tmetadata: unscoped.metadata,\n\t\t\t\t}\n\t\t\t: { values: values, truncated: false, metadata: unscoped.metadata };\n\t}\n\n\t/**\n\t * Searches the instance's projects together for a user-scoped read, as Jira Cloud does per site: one JQL names\n\t * up to {@link JIRA_MAX_PROJECT_KEYS_PER_REQUEST} projects of one user scope. An unscoped read stays per project,\n\t * since its backstop is reported as `narrow-scope` per project.\n\t */\n\tprotected override getProjectIssuesSearches(\n\t\trequests: readonly ProjectIssuesRequest<JiraServerProjectDescriptor>[],\n\t): number[][] | undefined {\n\t\treturn groupProjectIssuesSearches(\n\t\t\trequests,\n\t\t\t({ options }) =>\n\t\t\t\toptions.user != null\n\t\t\t\t\t? [options.user, options.sort ?? '', options.state ?? '', options.filters?.join(',') ?? ''].join(\n\t\t\t\t\t\t\t'\\0',\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined,\n\t\t\tJIRA_MAX_PROJECT_KEYS_PER_REQUEST,\n\t\t);\n\t}\n\n\tprotected override async searchProviderProjectIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\trequests: readonly ProjectIssuesRequest<JiraServerProjectDescriptor>[],\n\t): Promise<IssueShape[][] | undefined> {\n\t\t// Every request of a search shares its user scope (see `getProjectIssuesSearches`).\n\t\tconst { options } = requests[0];\n\t\tif (options.user == null) return undefined;\n\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\t\t// By numeric id, as the per-project read names them (see there).\n\t\tconst projectIds = requests.map(request => request.project.id);\n\t\tconst states = toProviderIssueStates(options.state);\n\n\t\treturn searchJiraProjectIssues(\n\t\t\t(userScope, cursor) =>\n\t\t\t\tapi.getJiraServerIssuesForProjectsPaged(tokenWithInfo, baseUrl, projectIds, {\n\t\t\t\t\t...userScope,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tstates: states,\n\t\t\t\t\tsort: options.sort,\n\t\t\t\t}),\n\t\t\tprojectIds,\n\t\t\toptions.filters,\n\t\t\ttoUserIdentities(options.user),\n\t\t\t{ maxPages: maxPagesPerRequest, failureScope: { providerId: this.id }, providerName: 'Jira Server' },\n\t\t\tthis,\n\t\t);\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraServerResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchProviderMyIssuesWithTruncation(session, resources, cancellation))?.values;\n\t}\n\n\t/**\n\t * Account-wide \"my issues\" for this instance, drained to the backstop and reporting whether it got there.\n\t *\n\t * The override exists for the same reason the project drain reports `status`: the default\n\t * implementation wraps whatever the normalized read returned as `truncated: false`, so a run that stopped\n\t * at `maxPagesPerRequest` with pages left, or on a continuation the server never advanced, would be\n\t * published as a complete account. Cancellation is NOT truncation — the caller asked for the stop and\n\t * already knows the prefix is partial — so it breaks without setting the flag.\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources?: JiraServerResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\n\t\t// Errors propagate, unlike Jira Cloud's sibling. Cloud swallows per resource because it loops over the\n\t\t// account's many sites and one bad site must not discard the others; this connection addresses exactly\n\t\t// one instance, so there is no sibling to protect and swallowing would turn every failure — an expired\n\t\t// token, an unreachable host — into an empty success. `searchMyIssuesWithTruncationResult` recovers the\n\t\t// throw into `{ error }`, which is what lets the caller warn instead of reporting \"no issues\".\n\t\t// Only the provider: the instance is this connection's single resource, so naming it would publish a\n\t\t// failure of the whole connection as one confined to a sub-scope (see `ProviderWarning.scope`).\n\t\tconst accountScope = { providerId: this.id };\n\t\tconst results: IssueShape[] = [];\n\t\tlet cursor: string | undefined;\n\t\tlet truncated = false;\n\t\tlet metadata: CollectionMetadata | undefined;\n\n\t\tconst stopIncomplete = (reason: string) => {\n\t\t\ttruncated = true;\n\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\tcompleteness: 'partial',\n\t\t\t\tfailures: [toCollectionScopeFailure(accountScope, new Error(reason))],\n\t\t\t});\n\t\t};\n\n\t\tfor (let i = 0; i < maxPagesPerRequest; i++) {\n\t\t\tif (cancellation?.aborted) break;\n\n\t\t\tconst page = await api.getJiraServerIssuesForCurrentUser(tokenWithInfo, baseUrl, {\n\t\t\t\tcursor: cursor,\n\t\t\t\tstates: states,\n\t\t\t});\n\t\t\tif (page == null) {\n\t\t\t\t// Nothing fetched yet and no page is an empty account, not a failure; after a continuation it\n\t\t\t\t// means the server dropped the drain mid-way.\n\t\t\t\tif (cursor != null) {\n\t\t\t\t\tstopIncomplete('Jira Server returned no page after advertising a continuation');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tresults.push(\n\t\t\t\t...page.data\n\t\t\t\t\t.map(issue => toIssueShape(issue, this, { projection: 'account' }))\n\t\t\t\t\t.filter((r): r is IssueShape => r != null),\n\t\t\t);\n\n\t\t\tif (!page.hasMore) break;\n\n\t\t\t// More pages are claimed but no advancing cursor came back: the drain can't continue, so flag it\n\t\t\t// rather than stopping silently.\n\t\t\tif (page.nextCursor == null || page.nextCursor === cursor) {\n\t\t\t\tstopIncomplete('Jira Server returned no advancing issue continuation');\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcursor = page.nextCursor;\n\t\t\t// Pages remain but this was the last allowed iteration: the backstop stopped the drain.\n\t\t\tif (i === maxPagesPerRequest - 1) {\n\t\t\t\tstopIncomplete(\n\t\t\t\t\t`Jira Server account-wide issue drain stopped at the ${maxPagesPerRequest}-page backstop`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn { values: results, truncated: truncated, metadata: metadata };\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t\t{ key }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst issue = await api.getJiraServerIssue(toTokenWithInfo(this.id, session), this.apiBaseUrlFor(session), key);\n\t\treturn issue != null ? toIssueShape(issue, this, { projection: 'point' }) : undefined;\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\treturn this.readIssue(session, id, 'point');\n\t}\n\n\tprivate async readIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tid: string,\n\t\tprojection: IssueProjection,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getJiraServerIssue(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t\tid,\n\t\t);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this, { projection: projection }) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\t/**\n\t * The point read behind `getIssuesBatch`'s tracker targets (#5872). `resourceId` is this connection's single\n\t * synthetic resource — the host itself, see {@link JiraServerResourceDescriptor} — so it selects nothing further\n\t * here: the host was chosen upstream by `domain` (or the connection's configured domain) and the request is\n\t * addressed to the SESSION's base URL like every other read. No `resourceUrl` is needed either, unlike Jira\n\t * Cloud: its REST `self` link is an API endpoint, whereas `getJiraServerIssue` builds the browser link from\n\t * the base URL it was addressed to. A key that names no issue resolves to `undefined`, a proven absence.\n\t */\n\tprotected override getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resourceId: string,\n\t\tid: string,\n\t\t_resourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\treturn this.readIssue(session, id, 'batch');\n\t}\n\n\t/**\n\t * Caches are keyed by the ADDRESS the read was made against as well as the token: one integration instance\n\t * exists per configured host and two hosts can hold the same token, so a token-only key would serve one\n\t * host's projects for the other — and the host alone is not enough either, because a connection re-pointed\n\t * to another context path on the same host keeps its token and would otherwise be served the old path's\n\t * projects.\n\t */\n\tprivate projectsCacheKey(session: ProviderAuthenticationSession): string {\n\t\treturn `${this.apiBaseUrlFor(session)}:${session.accessToken}`;\n\t}\n\n\toverride invalidateDiscoveryCaches(): void {\n\t\tthis._projects.clear();\n\t}\n\n\tprotected override providerOnDisconnect(): void {\n\t\tthis._projects.clear();\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"jira-server.js","names":[],"sources":["../../../../../plus/integrations/src/providers/jira-server.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport { JIRA_MAX_PROJECT_KEYS_PER_REQUEST } from '@gitkraken/provider-apis';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { Issue, IssueProjection, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport type { Emitter } from '@gitlens/utils/event.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport { isIncompleteCollection, toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { IssuesSelfManagedHostIntegrationId } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport type { IntegrationKey } from '../models/integration.js';\nimport type {\n\tAccountWideIssuesResult,\n\tIssuesForProjectOptions,\n\tProjectIssuesDrain,\n\tProjectIssuesRequest,\n\tSearchMyIssuesOptions,\n} from '../models/issueReads.js';\nimport { groupProjectIssuesSearches, IssuesIntegration } from '../models/issuesIntegration.js';\nimport { areDomainsOnSameHost, baseUrlFromDomain } from '../utils/domain.utils.js';\nimport { providersMetadata, toAccount, toIssueShape, toProviderIssueStates } from './models.js';\nimport { isJiraMissingProjectError } from './providerErrors.js';\nimport type { ProvidersApi } from './providersApi.js';\nimport { DiscoveryCache } from './utils/discoveryCache.js';\nimport type { JiraDrainOptions, JiraIssuePageFetcher } from './utils/jiraIssueDrains.js';\nimport {\n\tdrainJiraIssues,\n\treadUserScopedJiraIssues,\n\tsearchJiraProjectIssues,\n\tsummarizePageMetadata,\n\ttoUniqueJiraIssueShapes,\n\twithFailureScope,\n} from './utils/jiraIssueDrains.js';\nimport { mergeCollectionMetadata } from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[IssuesSelfManagedHostIntegrationId.JiraServer];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n\n/**\n * Every clause is scoped by the HANDLE, and `options.userId` is deliberately ignored here — the one\n * place this provider diverges from Jira Cloud's, which applies it to `assignee`/`creator` (#5857).\n *\n * The two are not interchangeable because `Account.id` means something different per deployment. On\n * Cloud it is the `accountId`, which JQL resolves, so scoping by it fixes the deactivated-account case\n * where the display name no longer resolves. On Server it is the user KEY (`JIRAUSER10224`), which JQL\n * does not resolve at all: measured against a live Jira Server 8.20.13 site, `assignee in\n * (\"JIRAUSER10224\")` returns 0 with `\"The value 'JIRAUSER10224' does not exist for the field\n * 'assignee'\"` — the identical warning a genuinely nonexistent user produces — while the username and\n * the display name both return the real 4.\n *\n * So applying `userId` here would silently empty the read rather than harden it. Do not \"fix\" this\n * asymmetry to match Cloud without re-measuring against a Server instance.\n *\n * The handle itself is already the right one: `provider-apis` 0.60.0 maps `Account.username` from the\n * Server payload's `name` (falling back to `displayName` when absent), so these clauses are scoped by the\n * real username rather than a display name (GKDEV-3629). That is what makes the `mention` clause work at\n * all — it is a free-text `comment ~ \"...\"` over comment bodies, and a Jira mention is stored as\n * `[~username]`, which a display name never matches.\n */\nfunction toUserIdentities(user: string): { userField: string; mention: string } {\n\treturn { userField: user, mention: user };\n}\n\n/**\n * The single resource a Jira Server connection has: the instance itself.\n *\n * Jira Cloud's account spans many sites, so its resources come from an API call that lists them. A\n * self-hosted instance IS the one site the connection addresses, so there is nothing to enumerate — this\n * descriptor is synthesized from the configured host. Modelling it as a resource anyway keeps the\n * resource → project contract of {@link IssuesIntegration} intact, so every tracker read (`listOrgs`,\n * `listProjects`, `listIssueTrackerIssuesPage`) works unchanged.\n */\nexport interface JiraServerResourceDescriptor extends IssueResourceDescriptor {\n\turl: string;\n}\n\nexport interface JiraServerProjectDescriptor extends IssueResourceDescriptor {\n\tresourceId: string;\n}\n\nexport class JiraServerIntegration extends IssuesIntegration<IssuesSelfManagedHostIntegrationId.JiraServer> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\treadonly id = IssuesSelfManagedHostIntegrationId.JiraServer;\n\tprotected readonly key: IntegrationKey<IssuesSelfManagedHostIntegrationId.JiraServer>;\n\treadonly name: string = 'Jira Data Center';\n\n\tconstructor(\n\t\tctx: IntegrationServiceContext,\n\t\tauthenticationService: IntegrationAuthenticationService,\n\t\tgetProvidersApi: () => Promise<ProvidersApi>,\n\t\tdidChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t\tprivate readonly _domain: string,\n\t) {\n\t\tsuper(ctx, authenticationService, getProvidersApi, didChangeConnection);\n\t\tthis.key = `${this.id}:${this._domain}`;\n\t}\n\n\tget domain(): string {\n\t\treturn this._domain;\n\t}\n\n\t/**\n\t * The URL every request for this connection is addressed to, built from the SESSION's domain rather than\n\t * the integration's.\n\t *\n\t * `this.domain` is the host-keyed identity — `IntegrationService.get()` runs it through `hostFromDomain`\n\t * so one host can't build two instances — which means it has had any context path stripped, and so has\n\t * `session.domain`, which is stored as that same normalized host. Jira Data Center officially supports\n\t * being mounted below a context path (`https://jira.example.com/jira`), so the address comes from\n\t * {@link ProviderAuthenticationSession.baseUrl}, which carries the configured value verbatim; it falls\n\t * back to the host when the backend reported nothing more, which is the common case.\n\t *\n\t * Takes the session rather than reading `_session` so a per-connection (multi-account) read is addressed\n\t * to the host of the connection it resolved, not to whichever one happens to be current.\n\t */\n\tprotected apiBaseUrlFor(session: ProviderAuthenticationSession | undefined): string {\n\t\tconst configured = baseUrlFromDomain(session?.baseUrl, session?.protocol);\n\t\t// A `baseUrl` from another host is not this connection's address — refuse it rather than routing this\n\t\t// integration's reads somewhere its key never named.\n\t\tif (configured != null && areDomainsOnSameHost(session?.baseUrl, this.domain)) return configured;\n\n\t\treturn (\n\t\t\tbaseUrlFromDomain(session?.domain, session?.protocol) ??\n\t\t\tbaseUrlFromDomain(this.domain, session?.protocol) ??\n\t\t\t`${session?.protocol ?? 'https:'}//${this.domain}`\n\t\t);\n\t}\n\n\t/** The one resource this connection has — see {@link JiraServerResourceDescriptor}. */\n\tprivate resourceFor(session: ProviderAuthenticationSession | undefined): JiraServerResourceDescriptor {\n\t\treturn { id: this.domain, key: this.domain, name: this.domain, url: this.apiBaseUrlFor(session) };\n\t}\n\n\t// No `autolinks()` override yet, unlike Jira Cloud: an autolink prefix has to be the Jira project KEY, which\n\t// is what a commit message or branch name carries (`PROJ-123`). The project descriptors carry it now (see\n\t// `getProviderProjectsForResources`), so the override can follow Cloud's; until then none is registered.\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getJiraServerCurrentUser(toTokenWithInfo(this.id, session), this.apiBaseUrlFor(session));\n\n\t\tif (user == null) return undefined;\n\t\treturn toAccount(user, this);\n\t}\n\n\tprotected override getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_force?: boolean,\n\t): Promise<JiraServerResourceDescriptor[] | undefined> {\n\t\t// Synthesized from the configured host rather than fetched, so there is nothing to cache or force.\n\t\treturn Promise.resolve([this.resourceFor(session)]);\n\t}\n\n\t/** Expires, and is dropped on a re-sync, so a project created or deleted mid-session is picked up (#5907). */\n\tprivate readonly _projects = new DiscoveryCache<JiraServerProjectDescriptor[]>();\n\tprotected override async getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraServerResourceDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<JiraServerProjectDescriptor[] | undefined> {\n\t\t// Every resource here is this instance (see `getProviderResourcesForUser`), so one read serves them\n\t\t// all; a caller that passes none is asking for nothing.\n\t\tif (resources.length === 0) return [];\n\n\t\tconst cacheKey = this.projectsCacheKey(session);\n\t\tconst cached = this._projects.get(cacheKey);\n\t\tif (cached != null && !force) return cached;\n\n\t\tconst generation = this._projects.generation;\n\t\tconst api = await this.getProvidersApi();\n\t\tconst projects = await api.getJiraServerProjects(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t);\n\t\tif (projects == null) return undefined;\n\n\t\tconst resourceId = this.resourceFor(session).id;\n\t\tconst descriptors = projects.map<JiraServerProjectDescriptor>(p => ({\n\t\t\tid: p.id,\n\t\t\t// The project key, which selects the project (`listProjects`' `name`) and is what an issue key starts\n\t\t\t// with. The SDK does not validate it, so a missing or blank one falls back to the numeric id, never to the\n\t\t\t// display name, which can equal another project's key. The reads below still address the project by\n\t\t\t// `id`, which no other project can share.\n\t\t\tkey: p.key?.trim() || p.id,\n\t\t\tname: p.name,\n\t\t\tresourceId: resourceId,\n\t\t}));\n\n\t\tthis._projects.set(cacheKey, descriptors, { generation: generation });\n\n\t\treturn descriptors;\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraServerProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;\n\t}\n\n\tprotected override async getProviderIssuesForProjectWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraServerProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\n\t\t// The same drain as Jira Cloud's (`jiraIssueDrains.ts`): page to a defensive backstop, and report `truncated`\n\t\t// plus a structured failure rather than discarding an already-fetched prefix.\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\t// The numeric project id, not the name: JQL resolves `project = \"<value>\"` against key first, so a display\n\t\t// name that matches another project's key reads that project's issues instead. A Jira project key must begin\n\t\t// with a letter, so a purely numeric value can only be an id.\n\t\tconst fetchPage: JiraIssuePageFetcher = (scope, cursor) =>\n\t\t\tapi.getJiraServerIssuesForProjectPaged(tokenWithInfo, baseUrl, project.id, {\n\t\t\t\t...scope,\n\t\t\t\tcursor: cursor,\n\t\t\t\tstates: states,\n\t\t\t\tsort: options?.sort,\n\t\t\t});\n\t\tconst drainOptions: JiraDrainOptions = {\n\t\t\tmaxPages: maxPagesPerRequest,\n\t\t\tfailureScope: { providerId: this.id, resourceId: project.resourceId, projectId: project.id },\n\t\t\tproviderName: 'Jira Server',\n\t\t\t// A project this connection can no longer see (deleted, or its browse permission removed) holds\n\t\t\t// no issues for it, and the project list that named it is stale: drop that list so the next read\n\t\t\t// re-reads it, instead of failing the whole provider on every read until a restart (#5907).\n\t\t\tisEmptyFailure: ex => {\n\t\t\t\tif (!isJiraMissingProjectError(ex)) return false;\n\n\t\t\t\tLogger.warn(`Jira Server project '${project.name}' (${project.id}) no longer exists or is not visible`);\n\t\t\t\tthis._projects.delete(this.projectsCacheKey(session));\n\t\t\t\treturn true;\n\t\t\t},\n\t\t};\n\n\t\tif (options?.user != null) {\n\t\t\tconst read = await readUserScopedJiraIssues(\n\t\t\t\tfetchPage,\n\t\t\t\toptions.filters,\n\t\t\t\ttoUserIdentities(options.user),\n\t\t\t\tdrainOptions,\n\t\t\t);\n\t\t\tconst values = toUniqueJiraIssueShapes(read.issues, this);\n\t\t\tconst drain = { values: values, metadata: read.metadata, scope: drainOptions.failureScope };\n\t\t\t// `recovery: 'none'` for the whole set: the filter branches merge here, so even a pure backstop in one\n\t\t\t// branch is not fixable by narrowing this read's scope.\n\t\t\treturn read.truncated ? { ...drain, truncated: true, recovery: 'none' } : { ...drain, truncated: false };\n\t\t}\n\n\t\tconst unscoped = await drainJiraIssues(fetchPage, {}, drainOptions);\n\t\tconst values = unscoped.issues\n\t\t\t.map(issue => toIssueShape(issue, this, { projection: 'project' }))\n\t\t\t.filter((result): result is IssueShape => result !== undefined);\n\t\tconst drain = { values: values, metadata: unscoped.metadata, scope: drainOptions.failureScope };\n\t\treturn unscoped.status !== 'complete'\n\t\t\t? { ...drain, truncated: true, recovery: unscoped.status === 'backstop' ? 'narrow-scope' : 'none' }\n\t\t\t: { ...drain, truncated: false };\n\t}\n\n\t/**\n\t * Searches the instance's projects together for a user-scoped read, as Jira Cloud does per site: one JQL names\n\t * up to {@link JIRA_MAX_PROJECT_KEYS_PER_REQUEST} projects of one user scope. An unscoped read stays per project,\n\t * since its backstop is reported as `narrow-scope` per project.\n\t */\n\tprotected override getProjectIssuesSearches(\n\t\trequests: readonly ProjectIssuesRequest<JiraServerProjectDescriptor>[],\n\t): number[][] | undefined {\n\t\treturn groupProjectIssuesSearches(\n\t\t\trequests,\n\t\t\t({ options }) =>\n\t\t\t\toptions.user != null\n\t\t\t\t\t? [options.user, options.sort ?? '', options.state ?? '', options.filters?.join(',') ?? ''].join(\n\t\t\t\t\t\t\t'\\0',\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined,\n\t\t\tJIRA_MAX_PROJECT_KEYS_PER_REQUEST,\n\t\t);\n\t}\n\n\tprotected override async searchProviderProjectIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\trequests: readonly ProjectIssuesRequest<JiraServerProjectDescriptor>[],\n\t): Promise<IssueShape[][] | undefined> {\n\t\t// Every request of a search shares its user scope (see `getProjectIssuesSearches`).\n\t\tconst { options } = requests[0];\n\t\tif (options.user == null) return undefined;\n\t\t// One project's search is its own read, by the same numeric id, so it costs no more to read it as one. And it\n\t\t// reports what the search can't: a dropped issue's failure, which a search can only answer by being read again.\n\t\tif (requests.length === 1) return undefined;\n\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst api = await this.getProvidersApi();\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\t\t// By numeric id, as the per-project read names them (see there).\n\t\tconst projectIds = requests.map(request => request.project.id);\n\t\tconst states = toProviderIssueStates(options.state);\n\n\t\treturn searchJiraProjectIssues(\n\t\t\t(userScope, cursor) =>\n\t\t\t\tapi.getJiraServerIssuesForProjectsPaged(tokenWithInfo, baseUrl, projectIds, {\n\t\t\t\t\t...userScope,\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tstates: states,\n\t\t\t\t\tsort: options.sort,\n\t\t\t\t}),\n\t\t\tprojectIds,\n\t\t\toptions.filters,\n\t\t\ttoUserIdentities(options.user),\n\t\t\t{ maxPages: maxPagesPerRequest, failureScope: { providerId: this.id }, providerName: 'Jira Server' },\n\t\t\tthis,\n\t\t);\n\t}\n\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraServerResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchProviderMyIssuesWithTruncation(session, resources, cancellation))?.values;\n\t}\n\n\t/**\n\t * Account-wide \"my issues\" for this instance, drained to the backstop and reporting whether it got there.\n\t *\n\t * The override exists for the same reason the project drain reports `status`: the default\n\t * implementation wraps whatever the normalized read returned as `truncated: false`, so a run that stopped\n\t * at `maxPagesPerRequest` with pages left, or on a continuation the server never advanced, would be\n\t * published as a complete account. Cancellation is NOT truncation — the caller asked for the stop and\n\t * already knows the prefix is partial — so it breaks without setting the flag.\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources?: JiraServerResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\tconst baseUrl = this.apiBaseUrlFor(session);\n\n\t\t// Errors propagate, unlike Jira Cloud's sibling. Cloud swallows per resource because it loops over the\n\t\t// account's many sites and one bad site must not discard the others; this connection addresses exactly\n\t\t// one instance, so there is no sibling to protect and swallowing would turn every failure — an expired\n\t\t// token, an unreachable host — into an empty success. `searchMyIssuesWithTruncationResult` recovers the\n\t\t// throw into `{ error }`, which is what lets the caller warn instead of reporting \"no issues\".\n\t\t// Only the provider: the instance is this connection's single resource, so naming it would publish a\n\t\t// failure of the whole connection as one confined to a sub-scope (see `ProviderWarning.scope`).\n\t\tconst accountScope = { providerId: this.id };\n\t\tconst results: IssueShape[] = [];\n\t\tlet cursor: string | undefined;\n\t\tlet truncated = false;\n\t\tlet metadata: CollectionMetadata | undefined;\n\t\tconst pages: CollectionMetadata[] = [];\n\n\t\tconst stopIncomplete = (reason: string) => {\n\t\t\ttruncated = true;\n\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\tcompleteness: 'partial',\n\t\t\t\tfailures: [toCollectionScopeFailure(accountScope, new Error(reason))],\n\t\t\t});\n\t\t};\n\n\t\tfor (let i = 0; i < maxPagesPerRequest; i++) {\n\t\t\tif (cancellation?.aborted) break;\n\n\t\t\tconst page = await api.getJiraServerIssuesForCurrentUser(tokenWithInfo, baseUrl, {\n\t\t\t\tcursor: cursor,\n\t\t\t\tstates: states,\n\t\t\t});\n\t\t\tif (page == null) {\n\t\t\t\t// Nothing fetched yet and no page is an empty account, not a failure; after a continuation it\n\t\t\t\t// means the server dropped the drain mid-way.\n\t\t\t\tif (cursor != null) {\n\t\t\t\t\tstopIncomplete('Jira Server returned no page after advertising a continuation');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tresults.push(\n\t\t\t\t...page.data\n\t\t\t\t\t.map(issue => toIssueShape(issue, this, { projection: 'account' }))\n\t\t\t\t\t.filter((r): r is IssueShape => r != null),\n\t\t\t);\n\t\t\t// An issue the page dropped because it could not be read arrives here, not as a missing row; recorded under\n\t\t\t// the same scope as this read's other failures.\n\t\t\tif (page.metadata != null) {\n\t\t\t\t// Truncated too, as Jira Cloud's read is through `getPagedResult`: the account is missing that issue.\n\t\t\t\ttruncated ||= isIncompleteCollection(page.metadata);\n\t\t\t\tpages.push(withFailureScope(page.metadata, accountScope));\n\t\t\t}\n\n\t\t\tif (!page.hasMore) break;\n\n\t\t\t// More pages are claimed but no advancing cursor came back: the drain can't continue, so flag it\n\t\t\t// rather than stopping silently.\n\t\t\tif (page.nextCursor == null || page.nextCursor === cursor) {\n\t\t\t\tstopIncomplete('Jira Server returned no advancing issue continuation');\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcursor = page.nextCursor;\n\t\t\t// Pages remain but this was the last allowed iteration: the backstop stopped the drain.\n\t\t\tif (i === maxPagesPerRequest - 1) {\n\t\t\t\tstopIncomplete(\n\t\t\t\t\t`Jira Server account-wide issue drain stopped at the ${maxPagesPerRequest}-page backstop`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tvalues: results,\n\t\t\ttruncated: truncated,\n\t\t\tmetadata: mergeCollectionMetadata(summarizePageMetadata(pages), metadata),\n\t\t};\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t\t{ key }: { id: string; key: string },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst issue = await api.getJiraServerIssue(toTokenWithInfo(this.id, session), this.apiBaseUrlFor(session), key);\n\t\treturn issue != null ? toIssueShape(issue, this, { projection: 'point' }) : undefined;\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: JiraServerResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\treturn this.readIssue(session, id, 'point');\n\t}\n\n\tprivate async readIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tid: string,\n\t\tprojection: IssueProjection,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getJiraServerIssue(\n\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\tthis.apiBaseUrlFor(session),\n\t\t\tid,\n\t\t);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this, { projection: projection }) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\t/**\n\t * The point read behind `getIssuesBatch`'s tracker targets (#5872). `resourceId` is this connection's single\n\t * synthetic resource — the host itself, see {@link JiraServerResourceDescriptor} — so it selects nothing further\n\t * here: the host was chosen upstream by `domain` (or the connection's configured domain) and the request is\n\t * addressed to the SESSION's base URL like every other read. No `resourceUrl` is needed either, unlike Jira\n\t * Cloud: its REST `self` link is an API endpoint, whereas `getJiraServerIssue` builds the browser link from\n\t * the base URL it was addressed to. A key that names no issue resolves to `undefined`, a proven absence.\n\t */\n\tprotected override getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resourceId: string,\n\t\tid: string,\n\t\t_resourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\treturn this.readIssue(session, id, 'batch');\n\t}\n\n\t/**\n\t * Caches are keyed by the ADDRESS the read was made against as well as the token: one integration instance\n\t * exists per configured host and two hosts can hold the same token, so a token-only key would serve one\n\t * host's projects for the other — and the host alone is not enough either, because a connection re-pointed\n\t * to another context path on the same host keeps its token and would otherwise be served the old path's\n\t * projects.\n\t */\n\tprivate projectsCacheKey(session: ProviderAuthenticationSession): string {\n\t\treturn `${this.apiBaseUrlFor(session)}:${session.accessToken}`;\n\t}\n\n\toverride invalidateDiscoveryCaches(): void {\n\t\tthis._projects.clear();\n\t}\n\n\tprotected override providerOnDisconnect(): void {\n\t\tthis._projects.clear();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -75,8 +75,11 @@ export declare class JiraIntegration extends IssuesIntegration<IssuesCloudHostIn
|
|
|
75
75
|
protected searchProviderProjectIssues(session: ProviderAuthenticationSession, requests: readonly ProjectIssuesRequest<JiraProjectDescriptor>[]): Promise<IssueShape[][] | undefined>;
|
|
76
76
|
protected searchProviderMyIssues(session: ProviderAuthenticationSession, resources?: JiraOrganizationDescriptor[], cancellation?: AbortSignal): Promise<IssueShape[] | undefined>;
|
|
77
77
|
/**
|
|
78
|
-
* Overridden so `state` reaches the per-site read, which the default drops.
|
|
79
|
-
*
|
|
78
|
+
* Overridden so `state` reaches the per-site read, which the default drops. Each site is drained on its own, so a
|
|
79
|
+
* site that fails keeps what it read and is recorded as that site's failure instead of discarding the sites that
|
|
80
|
+
* answered; so is an issue a page dropped because it could not be read. A page backstop or a stalled continuation
|
|
81
|
+
* makes the read `truncated`. A refused site therefore goes through `confirmScopedAuthFailures`, which publishes it
|
|
82
|
+
* as that site's auth warning or, if the credential itself is refused, as the connection's.
|
|
80
83
|
*/
|
|
81
84
|
protected searchProviderMyIssuesWithTruncation(session: ProviderAuthenticationSession, resources?: JiraOrganizationDescriptor[], cancellation?: AbortSignal, options?: SearchMyIssuesOptions): Promise<AccountWideIssuesResult | undefined>;
|
|
82
85
|
protected getProviderLinkedIssueOrPullRequest(session: ProviderAuthenticationSession, resource: JiraOrganizationDescriptor, { key }: {
|
|
@@ -9,7 +9,7 @@ import { collectProviderPagedResult, mergeCollectionMetadata } from "./utils/pro
|
|
|
9
9
|
import { providersMetadata, toAccount, toIssueShape, toProviderIssueStates } from "./models.js";
|
|
10
10
|
import { IssuesIntegration, groupProjectIssuesSearches } from "../models/issuesIntegration.js";
|
|
11
11
|
import { DiscoveryCache } from "./utils/discoveryCache.js";
|
|
12
|
-
import { drainJiraIssues, readUserScopedJiraIssues, searchJiraProjectIssues, toUniqueJiraIssueShapes } from "./utils/jiraIssueDrains.js";
|
|
12
|
+
import { drainJiraIssues, readUserScopedJiraIssues, searchJiraProjectIssues, summarizePageMetadata, toUniqueJiraIssueShapes } from "./utils/jiraIssueDrains.js";
|
|
13
13
|
import { toJiraIssueEtagFields } from "./jiraIssueByKey.js";
|
|
14
14
|
import * as l10n from "@vscode/l10n";
|
|
15
15
|
import { JIRA_MAX_PROJECT_KEYS_PER_REQUEST } from "@gitkraken/provider-apis";
|
|
@@ -242,7 +242,7 @@ var JiraIntegration = class extends IssuesIntegration {
|
|
|
242
242
|
const tokenWithInfo = toTokenWithInfo(this.id, session);
|
|
243
243
|
const api = await this.getProvidersApi();
|
|
244
244
|
const states = toProviderIssueStates(options?.state);
|
|
245
|
-
const fetchPage = (scope, cursor) => api.getIssuesForProjectPaged(tokenWithInfo, project.
|
|
245
|
+
const fetchPage = (scope, cursor) => api.getIssuesForProjectPaged(tokenWithInfo, project.key, project.resourceId, {
|
|
246
246
|
...scope,
|
|
247
247
|
cursor,
|
|
248
248
|
states,
|
|
@@ -253,7 +253,7 @@ var JiraIntegration = class extends IssuesIntegration {
|
|
|
253
253
|
failureScope: {
|
|
254
254
|
providerId: this.id,
|
|
255
255
|
resourceId: project.resourceId,
|
|
256
|
-
projectId: project.
|
|
256
|
+
projectId: project.key
|
|
257
257
|
},
|
|
258
258
|
providerName: "Jira",
|
|
259
259
|
isEmptyFailure: (ex) => {
|
|
@@ -266,29 +266,33 @@ var JiraIntegration = class extends IssuesIntegration {
|
|
|
266
266
|
};
|
|
267
267
|
if (options?.user != null) {
|
|
268
268
|
const read = await readUserScopedJiraIssues(fetchPage, options.filters, toUserIdentities(options.user, options), drainOptions);
|
|
269
|
-
const
|
|
269
|
+
const drain = {
|
|
270
|
+
values: toUniqueJiraIssueShapes(read.issues, this),
|
|
271
|
+
metadata: read.metadata,
|
|
272
|
+
scope: drainOptions.failureScope
|
|
273
|
+
};
|
|
270
274
|
return read.truncated ? {
|
|
271
|
-
|
|
275
|
+
...drain,
|
|
272
276
|
truncated: true,
|
|
273
|
-
recovery: "none"
|
|
274
|
-
metadata: read.metadata
|
|
277
|
+
recovery: "none"
|
|
275
278
|
} : {
|
|
276
|
-
|
|
277
|
-
truncated: false
|
|
278
|
-
metadata: read.metadata
|
|
279
|
+
...drain,
|
|
280
|
+
truncated: false
|
|
279
281
|
};
|
|
280
282
|
}
|
|
281
283
|
const unscoped = await drainJiraIssues(fetchPage, {}, drainOptions);
|
|
282
|
-
const
|
|
284
|
+
const drain = {
|
|
285
|
+
values: unscoped.issues.map((issue) => toIssueShape(issue, this, { projection: "project" })).filter((result) => result !== void 0),
|
|
286
|
+
metadata: unscoped.metadata,
|
|
287
|
+
scope: drainOptions.failureScope
|
|
288
|
+
};
|
|
283
289
|
return unscoped.status !== "complete" ? {
|
|
284
|
-
|
|
290
|
+
...drain,
|
|
285
291
|
truncated: true,
|
|
286
|
-
recovery: unscoped.status === "backstop" ? "narrow-scope" : "none"
|
|
287
|
-
metadata: unscoped.metadata
|
|
292
|
+
recovery: unscoped.status === "backstop" ? "narrow-scope" : "none"
|
|
288
293
|
} : {
|
|
289
|
-
|
|
290
|
-
truncated: false
|
|
291
|
-
metadata: unscoped.metadata
|
|
294
|
+
...drain,
|
|
295
|
+
truncated: false
|
|
292
296
|
};
|
|
293
297
|
}
|
|
294
298
|
/**
|
|
@@ -312,6 +316,7 @@ var JiraIntegration = class extends IssuesIntegration {
|
|
|
312
316
|
async searchProviderProjectIssues(session, requests) {
|
|
313
317
|
const { project, options } = requests[0];
|
|
314
318
|
if (options.user == null) return void 0;
|
|
319
|
+
if (requests.length === 1) return void 0;
|
|
315
320
|
const tokenWithInfo = toTokenWithInfo(this.id, session);
|
|
316
321
|
const api = await this.getProvidersApi();
|
|
317
322
|
const projectKeys = requests.map((request) => request.project.key);
|
|
@@ -331,43 +336,60 @@ var JiraIntegration = class extends IssuesIntegration {
|
|
|
331
336
|
}, this);
|
|
332
337
|
}
|
|
333
338
|
async searchProviderMyIssues(session, resources, cancellation) {
|
|
334
|
-
|
|
339
|
+
const result = await this.searchProviderMyIssuesWithTruncation(session, resources, cancellation);
|
|
340
|
+
for (const failure of result?.metadata?.failures ?? []) _logger.warn(`searchProviderMyIssues: Jira site '${failure.scope?.resourceId ?? "?"}' was not fully read: ${failure.message ?? failure.kind}`);
|
|
341
|
+
return result?.values;
|
|
335
342
|
}
|
|
336
343
|
/**
|
|
337
|
-
* Overridden so `state` reaches the per-site read, which the default drops.
|
|
338
|
-
*
|
|
344
|
+
* Overridden so `state` reaches the per-site read, which the default drops. Each site is drained on its own, so a
|
|
345
|
+
* site that fails keeps what it read and is recorded as that site's failure instead of discarding the sites that
|
|
346
|
+
* answered; so is an issue a page dropped because it could not be read. A page backstop or a stalled continuation
|
|
347
|
+
* makes the read `truncated`. A refused site therefore goes through `confirmScopedAuthFailures`, which publishes it
|
|
348
|
+
* as that site's auth warning or, if the credential itself is refused, as the connection's.
|
|
339
349
|
*/
|
|
340
350
|
async searchProviderMyIssuesWithTruncation(session, resources, cancellation, options) {
|
|
341
351
|
const states = toProviderIssueStates(options?.state);
|
|
342
352
|
const myResources = resources ?? await this.getProviderResourcesForUser(session);
|
|
343
353
|
if (!myResources) return void 0;
|
|
344
354
|
const api = await this.getProvidersApi();
|
|
355
|
+
const tokenWithInfo = toTokenWithInfo(this.id, session);
|
|
345
356
|
const results = [];
|
|
357
|
+
let truncated = false;
|
|
358
|
+
let metadata;
|
|
359
|
+
const pages = [];
|
|
360
|
+
const readPage = async (resourceId, cursor) => {
|
|
361
|
+
const page = await api.getIssuesForResourceForCurrentUser(tokenWithInfo, resourceId, {
|
|
362
|
+
cursor,
|
|
363
|
+
states
|
|
364
|
+
});
|
|
365
|
+
if (!page.metadata?.failures?.length) return page;
|
|
366
|
+
const { failures: _failures, ...rest } = page.metadata;
|
|
367
|
+
pages.push(page.metadata);
|
|
368
|
+
return {
|
|
369
|
+
...page,
|
|
370
|
+
metadata: rest
|
|
371
|
+
};
|
|
372
|
+
};
|
|
346
373
|
for (const resource of myResources) {
|
|
347
374
|
if (cancellation?.aborted) break;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const formattedIssues = resourceIssues.values.map((issue) => toIssueShape(issue, this, { projection: "account" })).filter((result) => result != null);
|
|
362
|
-
if (formattedIssues.length > 0) results.push(...formattedIssues);
|
|
363
|
-
} while (requestCount < maxPagesPerRequest && hasMore);
|
|
364
|
-
} catch (ex) {
|
|
365
|
-
_logger.error(ex, "searchProviderMyIssues");
|
|
366
|
-
}
|
|
375
|
+
const site = await collectProviderPagedResult((cursor) => cancellation?.aborted ? Promise.resolve({
|
|
376
|
+
values: [],
|
|
377
|
+
paging: {
|
|
378
|
+
cursor: "{}",
|
|
379
|
+
more: false
|
|
380
|
+
}
|
|
381
|
+
}) : readPage(resource.id, cursor), maxPagesPerRequest, {
|
|
382
|
+
providerId: this.id,
|
|
383
|
+
resourceId: resource.id
|
|
384
|
+
});
|
|
385
|
+
results.push(...site.values.map((issue) => toIssueShape(issue, this, { projection: "account" })).filter((result) => result != null));
|
|
386
|
+
truncated ||= site.truncated === true;
|
|
387
|
+
metadata = mergeCollectionMetadata(metadata, site.metadata);
|
|
367
388
|
}
|
|
368
389
|
return {
|
|
369
390
|
values: results,
|
|
370
|
-
truncated
|
|
391
|
+
truncated,
|
|
392
|
+
metadata: mergeCollectionMetadata(summarizePageMetadata(pages), metadata)
|
|
371
393
|
};
|
|
372
394
|
}
|
|
373
395
|
async getProviderLinkedIssueOrPullRequest(session, resource, { key }) {
|
|
@@ -391,10 +413,7 @@ var JiraIntegration = class extends IssuesIntegration {
|
|
|
391
413
|
async getProviderIssueByResourceId(session, resourceId, id, resourceUrl) {
|
|
392
414
|
if (resourceUrl == null) throw new Error("Jira direct issue reads require a resource URL");
|
|
393
415
|
const apiResult = await (await this.getProvidersApi()).getJiraIssueByKey(toTokenWithInfo(this.id, session), resourceId, resourceUrl, id);
|
|
394
|
-
const issue = apiResult != null ? toIssueShape(apiResult, this, {
|
|
395
|
-
reliableStateCategory: true,
|
|
396
|
-
projection: "batch"
|
|
397
|
-
}) : void 0;
|
|
416
|
+
const issue = apiResult != null ? toIssueShape(apiResult, this, { projection: "batch" }) : void 0;
|
|
398
417
|
return issue != null ? {
|
|
399
418
|
...issue,
|
|
400
419
|
type: "issue"
|