@gitkraken/core-gitlens 0.9.2 → 0.9.4

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/dist/plus/git-github/api/github.js +1 -1
  3. package/dist/plus/git-github/api/github.js.map +1 -1
  4. package/dist/plus/integrations/integrationService.d.ts +8 -2
  5. package/dist/plus/integrations/integrationService.js +3 -1
  6. package/dist/plus/integrations/integrationService.js.map +1 -1
  7. package/dist/plus/integrations/manager.d.ts +6 -1
  8. package/dist/plus/integrations/models/integration.d.ts +8 -0
  9. package/dist/plus/integrations/models/integration.js +9 -0
  10. package/dist/plus/integrations/models/integration.js.map +1 -1
  11. package/dist/plus/integrations/models/issueReads.d.ts +14 -0
  12. package/dist/plus/integrations/providers/azure/azure.d.ts +3 -0
  13. package/dist/plus/integrations/providers/azure/azure.js +18 -0
  14. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  15. package/dist/plus/integrations/providers/azure/azure.utils.d.ts +12 -0
  16. package/dist/plus/integrations/providers/azure/azure.utils.js +46 -0
  17. package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -0
  18. package/dist/plus/integrations/providers/azure/models.d.ts +1 -0
  19. package/dist/plus/integrations/providers/azure/models.js +1 -1
  20. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  21. package/dist/plus/integrations/providers/azureDevOps.d.ts +3 -0
  22. package/dist/plus/integrations/providers/azureDevOps.js +7 -0
  23. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  24. package/dist/plus/integrations/providers/bitbucket/bitbucket.utils.d.ts +17 -0
  25. package/dist/plus/integrations/providers/bitbucket/bitbucket.utils.js +25 -0
  26. package/dist/plus/integrations/providers/bitbucket/bitbucket.utils.js.map +1 -0
  27. package/dist/plus/integrations/providers/bitbucket/models.d.ts +5 -5
  28. package/dist/plus/integrations/providers/bitbucket/models.js +2 -2
  29. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  30. package/dist/plus/integrations/providers/bitbucket-server.d.ts +3 -0
  31. package/dist/plus/integrations/providers/bitbucket-server.js +7 -0
  32. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  33. package/dist/plus/integrations/providers/bitbucket.d.ts +3 -0
  34. package/dist/plus/integrations/providers/bitbucket.js +7 -0
  35. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  36. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  37. package/dist/plus/integrations/providers/jira-server.d.ts +5 -3
  38. package/dist/plus/integrations/providers/jira-server.js +28 -8
  39. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  40. package/dist/plus/integrations/providers/jira.d.ts +26 -4
  41. package/dist/plus/integrations/providers/jira.js +148 -53
  42. package/dist/plus/integrations/providers/jira.js.map +1 -1
  43. package/dist/plus/integrations/providers/linear.d.ts +13 -2
  44. package/dist/plus/integrations/providers/linear.js +49 -12
  45. package/dist/plus/integrations/providers/linear.js.map +1 -1
  46. package/dist/plus/integrations/providers/models.d.ts +19 -1
  47. package/dist/plus/integrations/providers/models.js +14 -5
  48. package/dist/plus/integrations/providers/models.js.map +1 -1
  49. package/dist/plus/integrations/providers/providerErrors.d.ts +11 -0
  50. package/dist/plus/integrations/providers/providerErrors.js +22 -1
  51. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  52. package/dist/plus/integrations/providers/providersApi.d.ts +8 -1
  53. package/dist/plus/integrations/providers/providersApi.js +3 -0
  54. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  55. package/dist/plus/integrations/providers/trello.js +1 -0
  56. package/dist/plus/integrations/providers/trello.js.map +1 -1
  57. package/dist/plus/integrations/providers/utils/discoveryCache.d.ts +36 -0
  58. package/dist/plus/integrations/providers/utils/discoveryCache.js +59 -0
  59. package/dist/plus/integrations/providers/utils/discoveryCache.js.map +1 -0
  60. package/dist/plus/integrations/reads/cursors.d.ts +8 -0
  61. package/dist/plus/integrations/reads/cursors.js +5 -3
  62. package/dist/plus/integrations/reads/cursors.js.map +1 -1
  63. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  64. package/dist/plus/integrations/reads/drains.js +4 -4
  65. package/dist/plus/integrations/reads/drains.js.map +1 -1
  66. package/dist/plus/integrations/reads/filters.d.ts +8 -0
  67. package/dist/plus/integrations/reads/filters.js +2 -1
  68. package/dist/plus/integrations/reads/filters.js.map +1 -1
  69. package/dist/plus/integrations/reads/issueTracker.d.ts +9 -1
  70. package/dist/plus/integrations/reads/issueTracker.js +20 -3
  71. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  72. package/dist/plus/integrations/reads/warnings.d.ts +11 -0
  73. package/dist/plus/integrations/reads/warnings.js +14 -1
  74. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  75. package/docs/integrations.md +4 -0
  76. package/package.json +2 -2
@@ -1,11 +1,13 @@
1
1
  import { Logger as _logger } from "../../../utils/logger.js";
2
2
  import "../constants.js";
3
+ import { isJiraMissingProjectError } from "./providerErrors.js";
3
4
  import { throwIfCallerContractError, toCollectionScopeFailure } from "../collectionMetadata.js";
4
5
  import { toTokenWithInfo } from "../authentication/models.js";
5
6
  import "../providerFilters.js";
6
7
  import { collectProviderPagedResult, mergeCollectionMetadata } from "./utils/providerPaging.js";
7
- import { providersMetadata, toAccount, toIssueShape } from "./models.js";
8
+ import { providersMetadata, toAccount, toIssueShape, toProviderIssueStates } from "./models.js";
8
9
  import { IssuesIntegration } from "../models/issuesIntegration.js";
10
+ import { DiscoveryCache } from "./utils/discoveryCache.js";
9
11
  import * as l10n from "@vscode/l10n";
10
12
  //#region ../plus/integrations/src/providers/jira.ts
11
13
  const metadata = providersMetadata["jira"];
@@ -25,16 +27,43 @@ var JiraIntegration = class extends IssuesIntegration {
25
27
  get apiBaseUrl() {
26
28
  return "https://api.atlassian.com";
27
29
  }
28
- _autolinks;
30
+ /**
31
+ * Built from the sites and their projects, so it follows them: rebuilt when a site's project list changes or the
32
+ * caches are dropped, and once older than {@link discoveryCacheTtl}. An expired set is still served while it is
33
+ * rebuilt in the background, because autolinks are read on every render of a commit message and must not wait
34
+ * on the network, nor go empty just because nothing read the projects for a while (#5907).
35
+ */
36
+ _autolinks = /* @__PURE__ */ new Map();
37
+ _autolinksBuilds = /* @__PURE__ */ new Map();
29
38
  async autolinks() {
30
- if (!(this.maybeConnected ?? await this.isConnected()) || this._session == null || this._organizations == null || this._projects == null) return [];
31
- const cachedAutolinks = this._autolinks?.get(this._session.accessToken);
32
- if (cachedAutolinks != null) return cachedAutolinks;
33
- const autolinks = [];
34
- const organizations = this._organizations.get(this._session.accessToken);
35
- if (organizations != null) for (const organization of organizations) {
36
- const projects = this._projects.get(`${this._session.accessToken}:${organization.id}`);
37
- if (projects != null) for (const project of projects) {
39
+ if (!(this.maybeConnected ?? await this.isConnected()) || this._session == null) return [];
40
+ const session = this._session;
41
+ const cached = this._autolinks.get(session.accessToken);
42
+ if (cached == null) return this.buildAutolinks(session);
43
+ if (Date.now() - cached.builtAt >= 3e5) this.buildAutolinks(session);
44
+ return cached.autolinks;
45
+ }
46
+ /** One build per token at a time: concurrent renders share it rather than each re-reading sites and projects. */
47
+ buildAutolinks(session) {
48
+ const { accessToken } = session;
49
+ let build = this._autolinksBuilds.get(accessToken);
50
+ if (build == null) {
51
+ build = this.buildAutolinksCore(session).finally(() => this._autolinksBuilds.delete(accessToken));
52
+ this._autolinksBuilds.set(accessToken, build);
53
+ }
54
+ return build;
55
+ }
56
+ async buildAutolinksCore(session) {
57
+ const { accessToken } = session;
58
+ const generation = this._projects.generation;
59
+ try {
60
+ const organizations = await this.getProviderResourcesForUser(session);
61
+ if (!organizations?.length) return [];
62
+ const { values: projects, metadata } = await this.getProviderProjectsForResourcesWithMetadata(session, organizations);
63
+ const autolinks = [];
64
+ for (const project of projects) {
65
+ const organization = organizations.find((o) => o.id === project.resourceId);
66
+ if (organization == null) continue;
38
67
  const dashedPrefix = `${project.key}-`;
39
68
  const underscoredPrefix = `${project.key}_`;
40
69
  autolinks.push({
@@ -59,10 +88,15 @@ var JiraIntegration = class extends IssuesIntegration {
59
88
  descriptor: { ...organization }
60
89
  });
61
90
  }
91
+ if ((metadata == null || metadata.completeness === "complete") && this._projects.generation === generation) this._autolinks.set(accessToken, {
92
+ autolinks,
93
+ builtAt: Date.now()
94
+ });
95
+ return autolinks;
96
+ } catch (ex) {
97
+ _logger.error(ex, "JiraIntegration.autolinks");
98
+ return this._autolinks.get(accessToken)?.autolinks ?? [];
62
99
  }
63
- this._autolinks ??= /* @__PURE__ */ new Map();
64
- this._autolinks.set(this._session.accessToken, autolinks);
65
- return autolinks;
66
100
  }
67
101
  async getProviderAccountForResource(session, resource) {
68
102
  const user = await (await this.getProvidersApi()).getCurrentUserForResource(toTokenWithInfo(this.id, session), resource.id);
@@ -81,26 +115,37 @@ var JiraIntegration = class extends IssuesIntegration {
81
115
  isCredentialRefusal(refusal) {
82
116
  return refusal.status === 401 && /scope does not match/i.test(refusal.detail ?? "");
83
117
  }
84
- _organizations;
118
+ /**
119
+ * The sites and the projects discovered under them, both of which expire and are dropped on a re-sync, so a site
120
+ * or project created or deleted mid-session is picked up (#5907). Keyed by token, and projects by token and site.
121
+ */
122
+ _organizations = new DiscoveryCache();
123
+ _projects = new DiscoveryCache();
124
+ /** When the caches were last dropped; a persisted discovery read before then is as stale as they were. */
125
+ _discoveryInvalidatedAt = 0;
85
126
  async getProviderResourcesForUser(session, force = false) {
86
127
  const { accessToken } = session;
87
- this._organizations ??= /* @__PURE__ */ new Map();
88
- if (this._organizations.get(accessToken) == null || force) {
89
- const resources = await (await this.getProvidersApi()).getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session));
90
- this._organizations.set(accessToken, resources != null ? resources.map((r) => ({
91
- ...r,
92
- key: r.id
93
- })) : void 0);
128
+ const cachedResources = this._organizations.get(accessToken);
129
+ if (cachedResources != null && !force) return cachedResources;
130
+ const generation = this._organizations.generation;
131
+ const organizations = (await (await this.getProvidersApi()).getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session)))?.map((r) => ({
132
+ ...r,
133
+ key: r.id
134
+ }));
135
+ if (organizations == null) {
136
+ this._organizations.delete(accessToken);
137
+ return;
94
138
  }
95
- return this._organizations.get(accessToken);
139
+ this._organizations.set(accessToken, organizations, { generation });
140
+ return organizations;
96
141
  }
97
- _projects;
98
142
  async getProviderProjectsForResources(session, resources, force = false) {
99
143
  return (await this.getProviderProjectsForResourcesWithMetadata(session, resources, force)).values;
100
144
  }
101
145
  async getProviderProjectsForResourcesWithMetadata(session, resources, force = false) {
102
146
  const { accessToken } = session;
103
- const projectsCache = this._projects ??= /* @__PURE__ */ new Map();
147
+ const projectsCache = this._projects;
148
+ const generation = projectsCache.generation;
104
149
  let resourcesWithoutProjects = [];
105
150
  if (force) resourcesWithoutProjects = resources;
106
151
  else for (const resource of resources) {
@@ -139,7 +184,11 @@ var JiraIntegration = class extends IssuesIntegration {
139
184
  if (result.truncated === true && (result.metadata == null || result.metadata.completeness === "complete")) metadata = mergeCollectionMetadata(metadata, { completeness: "partial" });
140
185
  return;
141
186
  }
142
- projectsCache.set(`${accessToken}:${resource.id}`, projects);
187
+ if (!projectsCache.set(`${accessToken}:${resource.id}`, projects, { generation })) {
188
+ partialProjects.push(...projects);
189
+ return;
190
+ }
191
+ this._autolinks.delete(accessToken);
143
192
  });
144
193
  }
145
194
  const values = resources.reduce((projects, resource) => {
@@ -168,6 +217,7 @@ var JiraIntegration = class extends IssuesIntegration {
168
217
  resourceId: project.resourceId,
169
218
  projectId: project.name
170
219
  };
220
+ const states = toProviderIssueStates(options?.state);
171
221
  const drainIssues = async (scope) => {
172
222
  const collected = [];
173
223
  let cursor;
@@ -179,9 +229,19 @@ var JiraIntegration = class extends IssuesIntegration {
179
229
  result = await api.getIssuesForProjectPaged(tokenWithInfo, project.name, project.resourceId, {
180
230
  ...scope,
181
231
  cursor,
232
+ states,
182
233
  sort: options?.sort
183
234
  });
184
235
  } catch (ex) {
236
+ if (collected.length === 0 && isJiraMissingProjectError(ex)) {
237
+ _logger.warn(`Jira project '${project.key}' no longer exists or is not visible`);
238
+ this._projects.delete(`${session.accessToken}:${project.resourceId}`);
239
+ this._autolinks.delete(session.accessToken);
240
+ return {
241
+ issues: [],
242
+ status: "complete"
243
+ };
244
+ }
185
245
  if (collected.length === 0) throw ex;
186
246
  status = "incomplete";
187
247
  metadata = mergeCollectionMetadata(metadata, {
@@ -294,6 +354,14 @@ var JiraIntegration = class extends IssuesIntegration {
294
354
  };
295
355
  }
296
356
  async searchProviderMyIssues(session, resources, cancellation) {
357
+ return (await this.searchProviderMyIssuesWithTruncation(session, resources, cancellation))?.values;
358
+ }
359
+ /**
360
+ * Overridden so `state` reaches the per-site read, which the default drops. Still reports `truncated: false`,
361
+ * as the default did: the per-site loop has no way to say it stopped early.
362
+ */
363
+ async searchProviderMyIssuesWithTruncation(session, resources, cancellation, options) {
364
+ const states = toProviderIssueStates(options?.state);
297
365
  const myResources = resources ?? await this.getProviderResourcesForUser(session);
298
366
  if (!myResources) return void 0;
299
367
  const api = await this.getProvidersApi();
@@ -306,7 +374,10 @@ var JiraIntegration = class extends IssuesIntegration {
306
374
  let requestCount = 0;
307
375
  do {
308
376
  if (cancellation?.aborted) break;
309
- const resourceIssues = await api.getIssuesForResourceForCurrentUser(toTokenWithInfo(this.id, session), resource.id, { cursor });
377
+ const resourceIssues = await api.getIssuesForResourceForCurrentUser(toTokenWithInfo(this.id, session), resource.id, {
378
+ cursor,
379
+ states
380
+ });
310
381
  requestCount += 1;
311
382
  hasMore = resourceIssues.paging?.more ?? false;
312
383
  cursor = resourceIssues.paging?.cursor;
@@ -317,7 +388,10 @@ var JiraIntegration = class extends IssuesIntegration {
317
388
  _logger.error(ex, "searchProviderMyIssues");
318
389
  }
319
390
  }
320
- return results;
391
+ return {
392
+ values: results,
393
+ truncated: false
394
+ };
321
395
  }
322
396
  async getProviderLinkedIssueOrPullRequest(session, resource, { key }) {
323
397
  const issue = await (await this.getProvidersApi()).getIssue(toTokenWithInfo(this.id, session), {
@@ -347,43 +421,64 @@ var JiraIntegration = class extends IssuesIntegration {
347
421
  } : void 0;
348
422
  }
349
423
  async providerOnConnect() {
350
- this._autolinks = void 0;
424
+ this._autolinks.clear();
351
425
  if (this._session == null) return;
352
- const storedOrganizations = this.ctx.storage.get(`jira:${this._session.accessToken}:organizations`);
353
- const storedProjects = this.ctx.storage.get(`jira:${this._session.accessToken}:projects`);
354
- let organizations = storedOrganizations?.data?.map((o) => ({ ...o }));
355
- let projects = storedProjects?.data?.map((p) => ({ ...p }));
356
- if (storedOrganizations == null) {
357
- organizations = await this.getProviderResourcesForUser(this._session, true);
358
- await this.ctx.storage.deleteWithPrefix("jira");
359
- await this.ctx.storage.store(`jira:${this._session.accessToken}:organizations`, {
426
+ const session = this._session;
427
+ const { accessToken } = session;
428
+ const organizationsGeneration = this._organizations.generation;
429
+ const projectsGeneration = this._projects.generation;
430
+ const isFresh = (timestamp) => typeof timestamp === "number" && timestamp > this._discoveryInvalidatedAt && Date.now() - timestamp < 3e5;
431
+ const storedOrganizations = this.ctx.storage.get(`jira:${accessToken}:organizations`);
432
+ const storedProjects = storedOrganizations != null ? this.ctx.storage.get(`jira:${accessToken}:projects`) : void 0;
433
+ let organizations;
434
+ if (storedOrganizations?.data != null && isFresh(storedOrganizations.timestamp)) {
435
+ const seeded = storedOrganizations.data.map((o) => ({ ...o }));
436
+ organizations = seeded;
437
+ this._organizations.set(accessToken, seeded, {
438
+ generation: organizationsGeneration,
439
+ storedAt: storedOrganizations.timestamp
440
+ });
441
+ } else {
442
+ const readAt = Date.now();
443
+ organizations = await this.getProviderResourcesForUser(session, true);
444
+ if (storedOrganizations == null) await this.ctx.storage.deleteWithPrefix("jira");
445
+ if (this._organizations.generation === organizationsGeneration) await this.ctx.storage.store(`jira:${accessToken}:organizations`, {
360
446
  v: 1,
361
- timestamp: Date.now(),
447
+ timestamp: readAt,
362
448
  data: organizations
363
449
  });
364
450
  }
365
- this._organizations ??= /* @__PURE__ */ new Map();
366
- this._organizations.set(this._session.accessToken, organizations);
367
- if (storedProjects == null && organizations?.length) {
368
- projects = await this.getProviderProjectsForResources(this._session, organizations);
369
- await this.ctx.storage.store(`jira:${this._session.accessToken}:projects`, {
451
+ if (storedProjects?.data != null && isFresh(storedProjects.timestamp)) {
452
+ const projectsByResource = /* @__PURE__ */ new Map();
453
+ for (const project of storedProjects.data) {
454
+ const projects = projectsByResource.get(project.resourceId);
455
+ if (projects == null) projectsByResource.set(project.resourceId, [{ ...project }]);
456
+ else if (!projects.some((p) => p.id === project.id)) projects.push({ ...project });
457
+ }
458
+ for (const [resourceId, projects] of projectsByResource) this._projects.set(`${accessToken}:${resourceId}`, projects, {
459
+ generation: projectsGeneration,
460
+ storedAt: storedProjects.timestamp
461
+ });
462
+ } else if (organizations?.length) {
463
+ const readAt = Date.now();
464
+ const projects = await this.getProviderProjectsForResources(session, organizations, true);
465
+ if (this._projects.generation === projectsGeneration) await this.ctx.storage.store(`jira:${accessToken}:projects`, {
370
466
  v: 1,
371
- timestamp: Date.now(),
467
+ timestamp: readAt,
372
468
  data: projects
373
469
  });
374
470
  }
375
- this._projects ??= /* @__PURE__ */ new Map();
376
- for (const project of projects ?? []) {
377
- const projectKey = `${this._session.accessToken}:${project.resourceId}`;
378
- const projects = this._projects.get(projectKey);
379
- if (projects == null) this._projects.set(projectKey, [project]);
380
- else if (!projects.some((p) => p.id === project.id)) projects.push(project);
381
- }
471
+ }
472
+ invalidateDiscoveryCaches() {
473
+ this._discoveryInvalidatedAt = Date.now();
474
+ this._organizations.clear();
475
+ this._projects.clear();
476
+ this._autolinks.clear();
382
477
  }
383
478
  providerOnDisconnect() {
384
- this._organizations = void 0;
385
- this._projects = void 0;
386
- this._autolinks = void 0;
479
+ this._organizations.clear();
480
+ this._projects.clear();
481
+ this._autolinks.clear();
387
482
  }
388
483
  };
389
484
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"jira.js","names":[],"sources":["../../../../../plus/integrations/src/providers/jira.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport * as l10n from '@vscode/l10n';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { AutolinkReference, DynamicAutolinkReference } from '@gitlens/git/models/autolink.js';\nimport type { Issue, 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 { Logger } from '@gitlens/utils/logger.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport type { ProviderRefusal } from '../collectionMetadata.js';\nimport { throwIfCallerContractError, toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport type { IssuesForProjectOptions, ProjectIssuesDrain } from '../models/issueReads.js';\nimport { IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { ProviderApiCollectionResult, ProviderIssue } from './models.js';\nimport { IssueFilter, providersMetadata, toAccount, toIssueShape } from './models.js';\nimport { collectProviderPagedResult, mergeCollectionMetadata } from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Jira];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n\ntype JiraProjectIssuesDrain<T> = {\n\tissues: T[];\n\tstatus: 'complete' | 'backstop' | 'incomplete';\n\tmetadata?: CollectionMetadata;\n};\n\nexport type JiraBaseDescriptor = IssueResourceDescriptor;\n\nexport interface JiraOrganizationDescriptor extends JiraBaseDescriptor {\n\turl: string;\n\tavatarUrl: string;\n}\n\nexport interface JiraProjectDescriptor extends JiraBaseDescriptor {\n\tresourceId: string;\n}\n\nexport class JiraIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Jira> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\treadonly id = IssuesCloudHostIntegrationId.Jira;\n\tprotected readonly key = this.id;\n\treadonly name: string = 'Jira';\n\n\tget domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\tprotected get apiBaseUrl(): string {\n\t\treturn 'https://api.atlassian.com';\n\t}\n\n\tprivate _autolinks: Map<string, (AutolinkReference | DynamicAutolinkReference)[]> | undefined;\n\toverride async autolinks(): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected || this._session == null || this._organizations == null || this._projects == null) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst cachedAutolinks = this._autolinks?.get(this._session.accessToken);\n\t\tif (cachedAutolinks != null) return cachedAutolinks;\n\n\t\tconst autolinks: (AutolinkReference | DynamicAutolinkReference)[] = [];\n\t\tconst organizations = this._organizations.get(this._session.accessToken);\n\t\tif (organizations != null) {\n\t\t\tfor (const organization of organizations) {\n\t\t\t\tconst projects = this._projects.get(`${this._session.accessToken}:${organization.id}`);\n\t\t\t\tif (projects != null) {\n\t\t\t\t\tfor (const project of projects) {\n\t\t\t\t\t\tconst dashedPrefix = `${project.key}-`;\n\t\t\t\t\t\tconst underscoredPrefix = `${project.key}_`;\n\t\t\t\t\t\tautolinks.push({\n\t\t\t\t\t\t\tprefix: dashedPrefix,\n\t\t\t\t\t\t\turl: `${organization.url}/browse/${dashedPrefix}<num>`,\n\t\t\t\t\t\t\talphanumeric: false,\n\t\t\t\t\t\t\tignoreCase: false,\n\t\t\t\t\t\t\ttitle: l10n.t('Open Issue {0} on {1}', `${dashedPrefix}<num>`, organization.name),\n\n\t\t\t\t\t\t\ttype: 'issue',\n\t\t\t\t\t\t\tdescription: l10n.t('{0} Issue {1}', organization.name, `${dashedPrefix}<num>`),\n\t\t\t\t\t\t\tdescriptor: { ...organization },\n\t\t\t\t\t\t});\n\t\t\t\t\t\tautolinks.push({\n\t\t\t\t\t\t\tprefix: underscoredPrefix,\n\t\t\t\t\t\t\turl: `${organization.url}/browse/${dashedPrefix}<num>`,\n\t\t\t\t\t\t\talphanumeric: false,\n\t\t\t\t\t\t\tignoreCase: false,\n\t\t\t\t\t\t\treferenceType: 'branch',\n\t\t\t\t\t\t\ttitle: l10n.t('Open Issue {0} on {1}', `${dashedPrefix}<num>`, organization.name),\n\n\t\t\t\t\t\t\ttype: 'issue',\n\t\t\t\t\t\t\tdescription: l10n.t('{0} Issue {1}', organization.name, `${dashedPrefix}<num>`),\n\t\t\t\t\t\t\tdescriptor: { ...organization },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis._autolinks ??= new Map<string, (AutolinkReference | DynamicAutolinkReference)[]>();\n\t\tthis._autolinks.set(this._session.accessToken, autolinks);\n\n\t\treturn autolinks;\n\t}\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getCurrentUserForResource(toTokenWithInfo(this.id, session), resource.id);\n\n\t\tif (user == null) return undefined;\n\t\treturn toAccount(user, this);\n\t}\n\n\t/** The site-list request the resource cache would otherwise answer; see `IntegrationBase.validateCredential`. */\n\tprotected override async validateCredential(session: ProviderAuthenticationSession): Promise<void> {\n\t\tconst api = await this.getProvidersApi();\n\t\tif ((await api.getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session))) == null) {\n\t\t\tthrow new Error('Jira did not confirm the credential');\n\t\t}\n\t}\n\n\t/**\n\t * Atlassian's `401` for a token missing a scope the request needs, which a reconnect fixes by consenting to it\n\t * again; see `IntegrationBase.isCredentialRefusal`. Atlassian gives the same answer to a malformed request URL,\n\t * so a request the SDK built wrong would read as the credential's too.\n\t */\n\tprotected override isCredentialRefusal(refusal: ProviderRefusal): boolean {\n\t\treturn refusal.status === 401 && /scope does not match/i.test(refusal.detail ?? '');\n\t}\n\n\tprivate _organizations: Map<string, JiraOrganizationDescriptor[] | undefined> | undefined;\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<JiraOrganizationDescriptor[] | undefined> {\n\t\tconst { accessToken } = session;\n\t\tthis._organizations ??= new Map<string, JiraOrganizationDescriptor[] | undefined>();\n\n\t\tconst cachedResources = this._organizations.get(accessToken);\n\n\t\tif (cachedResources == null || force) {\n\t\t\tconst api = await this.getProvidersApi();\n\t\t\tconst resources = await api.getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session));\n\t\t\tthis._organizations.set(\n\t\t\t\taccessToken,\n\t\t\t\tresources != null ? resources.map(r => ({ ...r, key: r.id })) : undefined,\n\t\t\t);\n\t\t}\n\n\t\treturn this._organizations.get(accessToken);\n\t}\n\n\tprivate _projects: Map<string, JiraProjectDescriptor[] | undefined> | undefined;\n\tprotected override async getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<JiraProjectDescriptor[] | undefined> {\n\t\treturn (await this.getProviderProjectsForResourcesWithMetadata(session, resources, force)).values;\n\t}\n\n\tprotected override async getProviderProjectsForResourcesWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<ProviderApiCollectionResult<JiraProjectDescriptor>> {\n\t\tconst { accessToken } = session;\n\t\tconst projectsCache = (this._projects ??= new Map<string, JiraProjectDescriptor[] | undefined>());\n\n\t\tlet resourcesWithoutProjects = [];\n\t\tif (force) {\n\t\t\tresourcesWithoutProjects = resources;\n\t\t} else {\n\t\t\tfor (const resource of resources) {\n\t\t\t\tconst resourceKey = `${accessToken}:${resource.id}`;\n\t\t\t\tconst cachedProjects = projectsCache.get(resourceKey);\n\t\t\t\tif (cachedProjects == null) {\n\t\t\t\t\tresourcesWithoutProjects.push(resource);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet metadata: CollectionMetadata | undefined;\n\t\tconst partialProjects: JiraProjectDescriptor[] = [];\n\t\tif (resourcesWithoutProjects.length > 0) {\n\t\t\tconst api = await this.getProvidersApi();\n\t\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\t\tconst drains = await Promise.allSettled(\n\t\t\t\tresourcesWithoutProjects.map(async resource => ({\n\t\t\t\t\tresource: resource,\n\t\t\t\t\tresult: await collectProviderPagedResult(\n\t\t\t\t\t\tcursor => api.getJiraProjectsForResource(tokenWithInfo, resource.id, { cursor: cursor }),\n\t\t\t\t\t\tmaxPagesPerRequest,\n\t\t\t\t\t\t{ providerId: this.id, resourceId: resource.id },\n\t\t\t\t\t),\n\t\t\t\t})),\n\t\t\t);\n\n\t\t\tdrains.forEach((drain, index) => {\n\t\t\t\tif (drain.status === 'rejected') {\n\t\t\t\t\tconst resource = resourcesWithoutProjects[index];\n\t\t\t\t\tif (resource == null) return;\n\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure({ providerId: this.id, resourceId: resource.id }, drain.reason),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst { resource, result } = drain.value;\n\t\t\t\tmetadata = mergeCollectionMetadata(metadata, result.metadata);\n\t\t\t\tconst projects = result.values\n\t\t\t\t\t.filter(project => project.resourceId === resource.id)\n\t\t\t\t\t.map(project => ({ ...project }));\n\t\t\t\tconst incomplete =\n\t\t\t\t\tresult.truncated === true ||\n\t\t\t\t\t(result.metadata != null && result.metadata.completeness !== 'complete');\n\t\t\t\tif (incomplete) {\n\t\t\t\t\tpartialProjects.push(...projects);\n\t\t\t\t\tif (\n\t\t\t\t\t\tresult.truncated === true &&\n\t\t\t\t\t\t(result.metadata == null || result.metadata.completeness === 'complete')\n\t\t\t\t\t) {\n\t\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, { completeness: 'partial' });\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tprojectsCache.set(`${accessToken}:${resource.id}`, projects);\n\t\t\t});\n\t\t}\n\n\t\tconst values = resources.reduce<JiraProjectDescriptor[]>((projects, resource) => {\n\t\t\tconst resourceProjects = projectsCache.get(`${accessToken}:${resource.id}`);\n\t\t\tif (resourceProjects != null) {\n\t\t\t\tprojects.push(...resourceProjects);\n\t\t\t}\n\t\t\treturn projects;\n\t\t}, partialProjects);\n\n\t\tconst projectsByIdentity = new Map<string, JiraProjectDescriptor>();\n\t\tfor (const project of values) {\n\t\t\tconst identity = `${project.resourceId}:${project.id}`;\n\t\t\tif (!projectsByIdentity.has(identity)) {\n\t\t\t\tprojectsByIdentity.set(identity, project);\n\t\t\t}\n\t\t}\n\t\treturn { values: [...projectsByIdentity.values()], metadata: metadata };\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraProjectDescriptor,\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: JiraProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\tconst api = await this.getProvidersApi();\n\n\t\tconst projectScope = {\n\t\t\tproviderId: this.id,\n\t\t\tresourceId: project.resourceId,\n\t\t\tprojectId: project.name,\n\t\t};\n\t\tconst drainIssues = async (scope: {\n\t\t\tauthorLogin?: string;\n\t\t\tassigneeLogins?: string[];\n\t\t\tmentionLogin?: string;\n\t\t}): Promise<JiraProjectIssuesDrain<ProviderIssue>> => {\n\t\t\tconst collected: ProviderIssue[] = [];\n\t\t\tlet cursor: string | undefined;\n\t\t\tlet status: JiraProjectIssuesDrain<ProviderIssue>['status'] = 'complete';\n\t\t\tlet metadata: CollectionMetadata | undefined;\n\t\t\tfor (let i = 0; i < maxPagesPerRequest; i++) {\n\t\t\t\tlet result: Awaited<ReturnType<typeof api.getIssuesForProjectPaged>> | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tresult = await api.getIssuesForProjectPaged(tokenWithInfo, project.name, project.resourceId, {\n\t\t\t\t\t\t...scope,\n\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\tsort: options?.sort,\n\t\t\t\t\t});\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// A page failure after the first page leaves the already-drained prefix intact; record the\n\t\t\t\t\t// failure at the project scope instead of re-throwing and discarding the prefix. If nothing was\n\t\t\t\t\t// fetched yet, the original throw behavior is preserved so the caller sees a hard error rather\n\t\t\t\t\t// than an empty partial success.\n\t\t\t\t\tif (collected.length === 0) throw ex;\n\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [toCollectionScopeFailure(projectScope, ex)],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (result == null) {\n\t\t\t\t\tif (cursor == null) break;\n\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\tprojectScope,\n\t\t\t\t\t\t\t\tnew Error('Jira returned no page after advertising a continuation'),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcollected.push(...result.data);\n\t\t\t\tif (!result.hasMore) break;\n\n\t\t\t\t// The provider claims more pages but gave no advancing cursor: we can't continue, so the drain\n\t\t\t\t// is incomplete — flag it rather than silently stopping (matches drainPullRequests/Repositories).\n\t\t\t\tif (result.nextCursor == null || result.nextCursor === cursor) {\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\tprojectScope,\n\t\t\t\t\t\t\t\tnew Error('Jira returned no advancing issue continuation'),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcursor = result.nextCursor;\n\t\t\t\tif (i === maxPagesPerRequest - 1) {\n\t\t\t\t\tstatus = 'backstop';\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tissues: collected,\n\t\t\t\tstatus: status,\n\t\t\t\tmetadata: metadata,\n\t\t\t};\n\t\t};\n\n\t\tconst getSearchedUserIssuesForFilter = async (\n\t\t\tuser: string,\n\t\t\tfilter: IssueFilter,\n\t\t): Promise<JiraProjectIssuesDrain<IssueShape>> => {\n\t\t\t// `assignee` and `creator` are user FIELDS: Jira resolves an accountId against the directory, which is\n\t\t\t// the identity that keeps matching once a display name cannot be looked up — a deactivated account, or\n\t\t\t// a profile whose visibility hides the name. Measured against a live site: a deactivated assignee\n\t\t\t// returns its issues by accountId and an empty page by display name, and Jira reports that miss as a\n\t\t\t// successful empty search rather than an error, so the list simply appears empty.\n\t\t\t//\n\t\t\t// `mention` is NOT a user field. It is `comment ~ \"...\"`, a free-text search over comment bodies, so an\n\t\t\t// accountId matches nothing there and the display name is the only value that can. Hence the id is\n\t\t\t// applied to the first two and the handle is kept for the third, rather than swapping `user` wholesale.\n\t\t\tconst userField = options?.userId ?? user;\n\t\t\tconst result = await drainIssues({\n\t\t\t\tauthorLogin: filter === IssueFilter.Author ? userField : undefined,\n\t\t\t\tassigneeLogins: filter === IssueFilter.Assignee ? [userField] : undefined,\n\t\t\t\tmentionLogin: filter === IssueFilter.Mention ? user : undefined,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tissues: result.issues\n\t\t\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t\t\t.filter((r): r is IssueShape => r !== undefined),\n\t\t\t\tstatus: result.status,\n\t\t\t\tmetadata: result.metadata,\n\t\t\t};\n\t\t};\n\n\t\tif (options?.user != null) {\n\t\t\tconst user = options.user;\n\t\t\t// A resolved user always scopes the read. Default to the assignee filter (\"my issues\") when no\n\t\t\t// explicit filters are given — otherwise a caller that scopes by user but omits filters would fall\n\t\t\t// through to the unscoped fetch below and get every issue in the project instead of the user's.\n\t\t\tconst filters = options.filters?.length ? options.filters : [IssueFilter.Assignee];\n\t\t\tconst settled = await Promise.allSettled(\n\t\t\t\tfilters.map(filter => getSearchedUserIssuesForFilter(user, filter)),\n\t\t\t);\n\n\t\t\t// If every filter branch rejected, the read failed outright — propagate the first rejection instead\n\t\t\t// of returning an empty list, which the facade (getIssuesForProjectResult → runCaptured) would\n\t\t\t// otherwise surface as a successful \"no issues\" rather than a warning + fetchFailed. The first\n\t\t\t// reason is re-thrown as-is (not wrapped in an AggregateError) so the facade can still classify it\n\t\t\t// by type (auth/rate-limit) — wrapping would collapse every failure to a generic 'other'. The\n\t\t\t// remaining reasons would otherwise be discarded, so log them here to keep them diagnosable.\n\t\t\tif (settled.every(r => r.status === 'rejected')) {\n\t\t\t\tfor (let i = 1; i < settled.length; i++) {\n\t\t\t\t\tconst outcome = settled[i];\n\t\t\t\t\tif (outcome.status === 'rejected') {\n\t\t\t\t\t\tLogger.error(\n\t\t\t\t\t\t\toutcome.reason,\n\t\t\t\t\t\t\t`getProviderIssuesForProjectWithTruncation: filter '${filters[i]}' failed`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthrow settled[0].status === 'rejected' ? settled[0].reason : new Error('Jira issue read failed');\n\t\t\t}\n\n\t\t\tlet truncated = false;\n\t\t\tlet metadata: CollectionMetadata | undefined;\n\t\t\tconst resultsById = new Map<string, IssueShape>();\n\t\t\tfor (let i = 0; i < settled.length; i++) {\n\t\t\t\tconst outcome = settled[i];\n\t\t\t\tconst filter = filters[i];\n\t\t\t\t// A rejected filter branch (with at least one sibling succeeding) means this project's issues are\n\t\t\t\t// incomplete: keep the sibling results but record a structured failure so the facade can warn on\n\t\t\t\t// the specific filter (auth/rate-limit) instead of just a generic truncation flag.\n\t\t\t\tif (outcome.status !== 'fulfilled') {\n\t\t\t\t\t// Identical for every filter branch, so degrading it would report one failure per branch for a\n\t\t\t\t\t// single invalid call — see `throwIfCallerContractError`.\n\t\t\t\t\tthrowIfCallerContractError(outcome.reason);\n\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\tconst failure = toCollectionScopeFailure(\n\t\t\t\t\t\t{ providerId: this.id, resourceId: project.resourceId, projectId: project.name },\n\t\t\t\t\t\toutcome.reason,\n\t\t\t\t\t);\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...failure,\n\t\t\t\t\t\t\t\tmessage: `Issue filter '${filter}' could not be read${\n\t\t\t\t\t\t\t\t\tfailure.message != null ? `: ${failure.message}` : ''\n\t\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (outcome.value.status !== 'complete') {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t}\n\t\t\t\tif (outcome.value.metadata != null) {\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, outcome.value.metadata);\n\t\t\t\t}\n\t\t\t\tfor (const resultIssue of outcome.value.issues) {\n\t\t\t\t\tif (!resultsById.has(resultIssue.id)) {\n\t\t\t\t\t\tresultsById.set(resultIssue.id, resultIssue);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn truncated\n\t\t\t\t? { values: [...resultsById.values()], truncated: true, recovery: 'none', metadata: metadata }\n\t\t\t\t: { values: [...resultsById.values()], truncated: false, metadata: metadata };\n\t\t}\n\n\t\tconst unscoped = await drainIssues({});\n\t\tconst values = unscoped.issues\n\t\t\t.map(issue => toIssueShape(issue, this))\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\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraOrganizationDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined> {\n\t\tconst myResources = resources ?? (await this.getProviderResourcesForUser(session));\n\t\tif (!myResources) return undefined;\n\n\t\tconst api = await this.getProvidersApi();\n\n\t\tconst results: IssueShape[] = [];\n\t\tfor (const resource of myResources) {\n\t\t\tif (cancellation?.aborted) break;\n\n\t\t\ttry {\n\t\t\t\tlet cursor = undefined;\n\t\t\t\tlet hasMore = false;\n\t\t\t\tlet requestCount = 0;\n\t\t\t\tdo {\n\t\t\t\t\tif (cancellation?.aborted) break;\n\n\t\t\t\t\tconst resourceIssues = await api.getIssuesForResourceForCurrentUser(\n\t\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\t\tresource.id,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\trequestCount += 1;\n\t\t\t\t\thasMore = resourceIssues.paging?.more ?? false;\n\t\t\t\t\tcursor = resourceIssues.paging?.cursor;\n\t\t\t\t\tconst formattedIssues = resourceIssues.values\n\t\t\t\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t\t\t\t.filter((result): result is IssueShape => result != null);\n\t\t\t\t\tif (formattedIssues.length > 0) {\n\t\t\t\t\t\tresults.push(...formattedIssues);\n\t\t\t\t\t}\n\t\t\t\t} while (requestCount < maxPagesPerRequest && hasMore);\n\t\t\t} catch (ex) {\n\t\t\t\t// TODO: We need a better way to message the failure to the user here.\n\t\t\t\t// This is a stopgap to prevent one bag org from throwing and preventing any issues from being returned.\n\t\t\t\tLogger.error(ex, 'searchProviderMyIssues');\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\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.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: key,\n\t\t});\n\t\treturn issue != null ? toIssueShape(issue, this) : undefined;\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: id,\n\t\t});\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\tprotected override async getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresourceId: string,\n\t\tid: string,\n\t\tresourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\tif (resourceUrl == null) {\n\t\t\tthrow new Error('Jira direct issue reads require a resource URL');\n\t\t}\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getJiraIssueByKey(toTokenWithInfo(this.id, session), resourceId, resourceUrl, id);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this, { reliableStateCategory: true }) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\tprotected override async providerOnConnect(): Promise<void> {\n\t\tthis._autolinks = undefined;\n\t\tif (this._session == null) return;\n\n\t\tconst storedOrganizations = this.ctx.storage.get(`jira:${this._session.accessToken}:organizations`);\n\t\tconst storedProjects = this.ctx.storage.get(`jira:${this._session.accessToken}:projects`);\n\n\t\tlet organizations = storedOrganizations?.data?.map((o: JiraOrganizationDescriptor) => ({ ...o }));\n\n\t\tlet projects = storedProjects?.data?.map((p: JiraProjectDescriptor) => ({ ...p }));\n\n\t\tif (storedOrganizations == null) {\n\t\t\torganizations = await this.getProviderResourcesForUser(this._session, true);\n\t\t\t// Clear all other stored organizations and projects when our session changes\n\t\t\tawait this.ctx.storage.deleteWithPrefix('jira');\n\t\t\tawait this.ctx.storage.store(`jira:${this._session.accessToken}:organizations`, {\n\t\t\t\tv: 1,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t\tdata: organizations,\n\t\t\t});\n\t\t}\n\n\t\tthis._organizations ??= new Map<string, JiraOrganizationDescriptor[] | undefined>();\n\t\tthis._organizations.set(this._session.accessToken, organizations);\n\n\t\tif (storedProjects == null && organizations?.length) {\n\t\t\tprojects = await this.getProviderProjectsForResources(this._session, organizations);\n\t\t\tawait this.ctx.storage.store(`jira:${this._session.accessToken}:projects`, {\n\t\t\t\tv: 1,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t\tdata: projects,\n\t\t\t});\n\t\t}\n\n\t\tthis._projects ??= new Map<string, JiraProjectDescriptor[] | undefined>();\n\t\tfor (const project of projects ?? []) {\n\t\t\tconst projectKey = `${this._session.accessToken}:${project.resourceId}`;\n\t\t\tconst projects = this._projects.get(projectKey);\n\t\t\tif (projects == null) {\n\t\t\t\tthis._projects.set(projectKey, [project]);\n\t\t\t} else if (!projects.some(p => p.id === project.id)) {\n\t\t\t\tprojects.push(project);\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected override providerOnDisconnect(): void {\n\t\tthis._organizations = undefined;\n\t\tthis._projects = undefined;\n\t\tthis._autolinks = undefined;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"jira.js","names":[],"sources":["../../../../../plus/integrations/src/providers/jira.ts"],"sourcesContent":["import type { CollectionMetadata } from '@gitkraken/provider-apis';\nimport * as l10n from '@vscode/l10n';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { AutolinkReference, DynamicAutolinkReference } from '@gitlens/git/models/autolink.js';\nimport type { Issue, 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 { Logger } from '@gitlens/utils/logger.js';\nimport type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { toTokenWithInfo } from '../authentication/models.js';\nimport type { ProviderRefusal } from '../collectionMetadata.js';\nimport { throwIfCallerContractError, toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport type {\n\tAccountWideIssuesResult,\n\tIssuesForProjectOptions,\n\tProjectIssuesDrain,\n\tSearchMyIssuesOptions,\n} from '../models/issueReads.js';\nimport { IssuesIntegration } from '../models/issuesIntegration.js';\nimport type { ProviderApiCollectionResult, ProviderIssue } from './models.js';\nimport { IssueFilter, providersMetadata, toAccount, toIssueShape, toProviderIssueStates } from './models.js';\nimport { isJiraMissingProjectError } from './providerErrors.js';\nimport { DiscoveryCache, discoveryCacheTtl } from './utils/discoveryCache.js';\nimport { collectProviderPagedResult, mergeCollectionMetadata } from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Jira];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n\ntype JiraProjectIssuesDrain<T> = {\n\tissues: T[];\n\tstatus: 'complete' | 'backstop' | 'incomplete';\n\tmetadata?: CollectionMetadata;\n};\n\nexport type JiraBaseDescriptor = IssueResourceDescriptor;\n\nexport interface JiraOrganizationDescriptor extends JiraBaseDescriptor {\n\turl: string;\n\tavatarUrl: string;\n}\n\nexport interface JiraProjectDescriptor extends JiraBaseDescriptor {\n\tresourceId: string;\n}\n\nexport class JiraIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Jira> {\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\treadonly id = IssuesCloudHostIntegrationId.Jira;\n\tprotected readonly key = this.id;\n\treadonly name: string = 'Jira';\n\n\tget domain(): string {\n\t\treturn metadata.domain;\n\t}\n\n\tprotected get apiBaseUrl(): string {\n\t\treturn 'https://api.atlassian.com';\n\t}\n\n\t/**\n\t * Built from the sites and their projects, so it follows them: rebuilt when a site's project list changes or the\n\t * caches are dropped, and once older than {@link discoveryCacheTtl}. An expired set is still served while it is\n\t * rebuilt in the background, because autolinks are read on every render of a commit message and must not wait\n\t * on the network, nor go empty just because nothing read the projects for a while (#5907).\n\t */\n\tprivate readonly _autolinks = new Map<\n\t\tstring,\n\t\t{ autolinks: (AutolinkReference | DynamicAutolinkReference)[]; builtAt: number }\n\t>();\n\tprivate readonly _autolinksBuilds = new Map<string, Promise<(AutolinkReference | DynamicAutolinkReference)[]>>();\n\n\toverride async autolinks(): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected || this._session == null) return [];\n\n\t\tconst session = this._session;\n\t\tconst cached = this._autolinks.get(session.accessToken);\n\t\tif (cached == null) return this.buildAutolinks(session);\n\n\t\tif (Date.now() - cached.builtAt >= discoveryCacheTtl) {\n\t\t\tvoid this.buildAutolinks(session);\n\t\t}\n\t\treturn cached.autolinks;\n\t}\n\n\t/** One build per token at a time: concurrent renders share it rather than each re-reading sites and projects. */\n\tprivate buildAutolinks(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst { accessToken } = session;\n\t\tlet build = this._autolinksBuilds.get(accessToken);\n\t\tif (build == null) {\n\t\t\tbuild = this.buildAutolinksCore(session).finally(() => this._autolinksBuilds.delete(accessToken));\n\t\t\tthis._autolinksBuilds.set(accessToken, build);\n\t\t}\n\t\treturn build;\n\t}\n\n\tprivate async buildAutolinksCore(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\tconst { accessToken } = session;\n\t\tconst generation = this._projects.generation;\n\t\ttry {\n\t\t\tconst organizations = await this.getProviderResourcesForUser(session);\n\t\t\tif (!organizations?.length) return [];\n\n\t\t\t// Served from the cache for every site whose projects are still fresh; only the rest are read.\n\t\t\tconst { values: projects, metadata } = await this.getProviderProjectsForResourcesWithMetadata(\n\t\t\t\tsession,\n\t\t\t\torganizations,\n\t\t\t);\n\n\t\t\tconst autolinks: (AutolinkReference | DynamicAutolinkReference)[] = [];\n\t\t\tfor (const project of projects) {\n\t\t\t\tconst organization = organizations.find(o => o.id === project.resourceId);\n\t\t\t\tif (organization == null) continue;\n\n\t\t\t\tconst dashedPrefix = `${project.key}-`;\n\t\t\t\tconst underscoredPrefix = `${project.key}_`;\n\t\t\t\tautolinks.push({\n\t\t\t\t\tprefix: dashedPrefix,\n\t\t\t\t\turl: `${organization.url}/browse/${dashedPrefix}<num>`,\n\t\t\t\t\talphanumeric: false,\n\t\t\t\t\tignoreCase: false,\n\t\t\t\t\ttitle: l10n.t('Open Issue {0} on {1}', `${dashedPrefix}<num>`, organization.name),\n\n\t\t\t\t\ttype: 'issue',\n\t\t\t\t\tdescription: l10n.t('{0} Issue {1}', organization.name, `${dashedPrefix}<num>`),\n\t\t\t\t\tdescriptor: { ...organization },\n\t\t\t\t});\n\t\t\t\tautolinks.push({\n\t\t\t\t\tprefix: underscoredPrefix,\n\t\t\t\t\turl: `${organization.url}/browse/${dashedPrefix}<num>`,\n\t\t\t\t\talphanumeric: false,\n\t\t\t\t\tignoreCase: false,\n\t\t\t\t\treferenceType: 'branch',\n\t\t\t\t\ttitle: l10n.t('Open Issue {0} on {1}', `${dashedPrefix}<num>`, organization.name),\n\n\t\t\t\t\ttype: 'issue',\n\t\t\t\t\tdescription: l10n.t('{0} Issue {1}', organization.name, `${dashedPrefix}<num>`),\n\t\t\t\t\tdescriptor: { ...organization },\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// A set missing a site's projects is served but not kept, so the next call tries that site again; one\n\t\t\t// built from projects read before the caches were dropped is not kept either.\n\t\t\tconst complete = metadata == null || metadata.completeness === 'complete';\n\t\t\tif (complete && this._projects.generation === generation) {\n\t\t\t\tthis._autolinks.set(accessToken, { autolinks: autolinks, builtAt: Date.now() });\n\t\t\t}\n\t\t\treturn autolinks;\n\t\t} catch (ex) {\n\t\t\t// Autolinks are decoration: keep serving the last set rather than failing the render that asked.\n\t\t\tLogger.error(ex, 'JiraIntegration.autolinks');\n\t\t\treturn this._autolinks.get(accessToken)?.autolinks ?? [];\n\t\t}\n\t}\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst user = await api.getCurrentUserForResource(toTokenWithInfo(this.id, session), resource.id);\n\n\t\tif (user == null) return undefined;\n\t\treturn toAccount(user, this);\n\t}\n\n\t/** The site-list request the resource cache would otherwise answer; see `IntegrationBase.validateCredential`. */\n\tprotected override async validateCredential(session: ProviderAuthenticationSession): Promise<void> {\n\t\tconst api = await this.getProvidersApi();\n\t\tif ((await api.getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session))) == null) {\n\t\t\tthrow new Error('Jira did not confirm the credential');\n\t\t}\n\t}\n\n\t/**\n\t * Atlassian's `401` for a token missing a scope the request needs, which a reconnect fixes by consenting to it\n\t * again; see `IntegrationBase.isCredentialRefusal`. Atlassian gives the same answer to a malformed request URL,\n\t * so a request the SDK built wrong would read as the credential's too.\n\t */\n\tprotected override isCredentialRefusal(refusal: ProviderRefusal): boolean {\n\t\treturn refusal.status === 401 && /scope does not match/i.test(refusal.detail ?? '');\n\t}\n\n\t/**\n\t * The sites and the projects discovered under them, both of which expire and are dropped on a re-sync, so a site\n\t * or project created or deleted mid-session is picked up (#5907). Keyed by token, and projects by token and site.\n\t */\n\tprivate readonly _organizations = new DiscoveryCache<JiraOrganizationDescriptor[]>();\n\tprivate readonly _projects = new DiscoveryCache<JiraProjectDescriptor[]>();\n\t/** When the caches were last dropped; a persisted discovery read before then is as stale as they were. */\n\tprivate _discoveryInvalidatedAt = 0;\n\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<JiraOrganizationDescriptor[] | undefined> {\n\t\tconst { accessToken } = session;\n\n\t\tconst cachedResources = this._organizations.get(accessToken);\n\t\tif (cachedResources != null && !force) return cachedResources;\n\n\t\tconst generation = this._organizations.generation;\n\t\tconst api = await this.getProvidersApi();\n\t\tconst resources = await api.getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session));\n\t\tconst organizations = resources?.map(r => ({ ...r, key: r.id }));\n\t\tif (organizations == null) {\n\t\t\tthis._organizations.delete(accessToken);\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis._organizations.set(accessToken, organizations, { generation: generation });\n\t\treturn organizations;\n\t}\n\n\tprotected override async getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<JiraProjectDescriptor[] | undefined> {\n\t\treturn (await this.getProviderProjectsForResourcesWithMetadata(session, resources, force)).values;\n\t}\n\n\tprotected override async getProviderProjectsForResourcesWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources: JiraOrganizationDescriptor[],\n\t\tforce: boolean = false,\n\t): Promise<ProviderApiCollectionResult<JiraProjectDescriptor>> {\n\t\tconst { accessToken } = session;\n\t\tconst projectsCache = this._projects;\n\t\tconst generation = projectsCache.generation;\n\n\t\tlet resourcesWithoutProjects = [];\n\t\tif (force) {\n\t\t\tresourcesWithoutProjects = resources;\n\t\t} else {\n\t\t\tfor (const resource of resources) {\n\t\t\t\tconst resourceKey = `${accessToken}:${resource.id}`;\n\t\t\t\tconst cachedProjects = projectsCache.get(resourceKey);\n\t\t\t\tif (cachedProjects == null) {\n\t\t\t\t\tresourcesWithoutProjects.push(resource);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet metadata: CollectionMetadata | undefined;\n\t\tconst partialProjects: JiraProjectDescriptor[] = [];\n\t\tif (resourcesWithoutProjects.length > 0) {\n\t\t\tconst api = await this.getProvidersApi();\n\t\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\t\t\tconst drains = await Promise.allSettled(\n\t\t\t\tresourcesWithoutProjects.map(async resource => ({\n\t\t\t\t\tresource: resource,\n\t\t\t\t\tresult: await collectProviderPagedResult(\n\t\t\t\t\t\tcursor => api.getJiraProjectsForResource(tokenWithInfo, resource.id, { cursor: cursor }),\n\t\t\t\t\t\tmaxPagesPerRequest,\n\t\t\t\t\t\t{ providerId: this.id, resourceId: resource.id },\n\t\t\t\t\t),\n\t\t\t\t})),\n\t\t\t);\n\n\t\t\tdrains.forEach((drain, index) => {\n\t\t\t\tif (drain.status === 'rejected') {\n\t\t\t\t\tconst resource = resourcesWithoutProjects[index];\n\t\t\t\t\tif (resource == null) return;\n\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure({ providerId: this.id, resourceId: resource.id }, drain.reason),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst { resource, result } = drain.value;\n\t\t\t\tmetadata = mergeCollectionMetadata(metadata, result.metadata);\n\t\t\t\tconst projects = result.values\n\t\t\t\t\t.filter(project => project.resourceId === resource.id)\n\t\t\t\t\t.map(project => ({ ...project }));\n\t\t\t\tconst incomplete =\n\t\t\t\t\tresult.truncated === true ||\n\t\t\t\t\t(result.metadata != null && result.metadata.completeness !== 'complete');\n\t\t\t\tif (incomplete) {\n\t\t\t\t\tpartialProjects.push(...projects);\n\t\t\t\t\tif (\n\t\t\t\t\t\tresult.truncated === true &&\n\t\t\t\t\t\t(result.metadata == null || result.metadata.completeness === 'complete')\n\t\t\t\t\t) {\n\t\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, { completeness: 'partial' });\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Not stored when the cache was dropped while this read was in flight, but still returned below: it\n\t\t\t\t// is what this read found, only no longer trusted for the next one.\n\t\t\t\tif (!projectsCache.set(`${accessToken}:${resource.id}`, projects, { generation: generation })) {\n\t\t\t\t\tpartialProjects.push(...projects);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis._autolinks.delete(accessToken);\n\t\t\t});\n\t\t}\n\n\t\tconst values = resources.reduce<JiraProjectDescriptor[]>((projects, resource) => {\n\t\t\tconst resourceProjects = projectsCache.get(`${accessToken}:${resource.id}`);\n\t\t\tif (resourceProjects != null) {\n\t\t\t\tprojects.push(...resourceProjects);\n\t\t\t}\n\t\t\treturn projects;\n\t\t}, partialProjects);\n\n\t\tconst projectsByIdentity = new Map<string, JiraProjectDescriptor>();\n\t\tfor (const project of values) {\n\t\t\tconst identity = `${project.resourceId}:${project.id}`;\n\t\t\tif (!projectsByIdentity.has(identity)) {\n\t\t\t\tprojectsByIdentity.set(identity, project);\n\t\t\t}\n\t\t}\n\t\treturn { values: [...projectsByIdentity.values()], metadata: metadata };\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: JiraProjectDescriptor,\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: JiraProjectDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tconst tokenWithInfo = toTokenWithInfo(this.id, session);\n\n\t\tconst api = await this.getProvidersApi();\n\n\t\tconst projectScope = {\n\t\t\tproviderId: this.id,\n\t\t\tresourceId: project.resourceId,\n\t\t\tprojectId: project.name,\n\t\t};\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\tconst drainIssues = async (scope: {\n\t\t\tauthorLogin?: string;\n\t\t\tassigneeLogins?: string[];\n\t\t\tmentionLogin?: string;\n\t\t}): Promise<JiraProjectIssuesDrain<ProviderIssue>> => {\n\t\t\tconst collected: ProviderIssue[] = [];\n\t\t\tlet cursor: string | undefined;\n\t\t\tlet status: JiraProjectIssuesDrain<ProviderIssue>['status'] = 'complete';\n\t\t\tlet metadata: CollectionMetadata | undefined;\n\t\t\tfor (let i = 0; i < maxPagesPerRequest; i++) {\n\t\t\t\tlet result: Awaited<ReturnType<typeof api.getIssuesForProjectPaged>> | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tresult = await api.getIssuesForProjectPaged(tokenWithInfo, project.name, project.resourceId, {\n\t\t\t\t\t\t...scope,\n\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\tstates: states,\n\t\t\t\t\t\tsort: options?.sort,\n\t\t\t\t\t});\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// A project this token can no longer see (deleted, or its browse permission removed) holds no issues\n\t\t\t\t\t// for it, and the site's project list that named it is stale: drop that list so the next read\n\t\t\t\t\t// re-reads it, instead of failing the whole provider on every read until a restart (#5907).\n\t\t\t\t\tif (collected.length === 0 && isJiraMissingProjectError(ex)) {\n\t\t\t\t\t\tLogger.warn(`Jira project '${project.key}' no longer exists or is not visible`);\n\t\t\t\t\t\tthis._projects.delete(`${session.accessToken}:${project.resourceId}`);\n\t\t\t\t\t\tthis._autolinks.delete(session.accessToken);\n\t\t\t\t\t\treturn { issues: [], status: 'complete' };\n\t\t\t\t\t}\n\n\t\t\t\t\t// A page failure after the first page leaves the already-drained prefix intact; record the\n\t\t\t\t\t// failure at the project scope instead of re-throwing and discarding the prefix. If nothing was\n\t\t\t\t\t// fetched yet, the original throw behavior is preserved so the caller sees a hard error rather\n\t\t\t\t\t// than an empty partial success.\n\t\t\t\t\tif (collected.length === 0) throw ex;\n\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [toCollectionScopeFailure(projectScope, ex)],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (result == null) {\n\t\t\t\t\tif (cursor == null) break;\n\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\tprojectScope,\n\t\t\t\t\t\t\t\tnew Error('Jira returned no page after advertising a continuation'),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcollected.push(...result.data);\n\t\t\t\tif (!result.hasMore) break;\n\n\t\t\t\t// The provider claims more pages but gave no advancing cursor: we can't continue, so the drain\n\t\t\t\t// is incomplete — flag it rather than silently stopping (matches drainPullRequests/Repositories).\n\t\t\t\tif (result.nextCursor == null || result.nextCursor === cursor) {\n\t\t\t\t\tstatus = 'incomplete';\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\ttoCollectionScopeFailure(\n\t\t\t\t\t\t\t\tprojectScope,\n\t\t\t\t\t\t\t\tnew Error('Jira returned no advancing issue continuation'),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcursor = result.nextCursor;\n\t\t\t\tif (i === maxPagesPerRequest - 1) {\n\t\t\t\t\tstatus = 'backstop';\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tissues: collected,\n\t\t\t\tstatus: status,\n\t\t\t\tmetadata: metadata,\n\t\t\t};\n\t\t};\n\n\t\tconst getSearchedUserIssuesForFilter = async (\n\t\t\tuser: string,\n\t\t\tfilter: IssueFilter,\n\t\t): Promise<JiraProjectIssuesDrain<IssueShape>> => {\n\t\t\t// `assignee` and `creator` are user FIELDS: Jira resolves an accountId against the directory, which is\n\t\t\t// the identity that keeps matching once a display name cannot be looked up — a deactivated account, or\n\t\t\t// a profile whose visibility hides the name. Measured against a live site: a deactivated assignee\n\t\t\t// returns its issues by accountId and an empty page by display name, and Jira reports that miss as a\n\t\t\t// successful empty search rather than an error, so the list simply appears empty.\n\t\t\t//\n\t\t\t// `mention` is NOT a user field. It is `comment ~ \"...\"`, a free-text search over comment bodies, so an\n\t\t\t// accountId matches nothing there and the display name is the only value that can. Hence the id is\n\t\t\t// applied to the first two and the handle is kept for the third, rather than swapping `user` wholesale.\n\t\t\tconst userField = options?.userId ?? user;\n\t\t\tconst result = await drainIssues({\n\t\t\t\tauthorLogin: filter === IssueFilter.Author ? userField : undefined,\n\t\t\t\tassigneeLogins: filter === IssueFilter.Assignee ? [userField] : undefined,\n\t\t\t\tmentionLogin: filter === IssueFilter.Mention ? user : undefined,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tissues: result.issues\n\t\t\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t\t\t.filter((r): r is IssueShape => r !== undefined),\n\t\t\t\tstatus: result.status,\n\t\t\t\tmetadata: result.metadata,\n\t\t\t};\n\t\t};\n\n\t\tif (options?.user != null) {\n\t\t\tconst user = options.user;\n\t\t\t// A resolved user always scopes the read. Default to the assignee filter (\"my issues\") when no\n\t\t\t// explicit filters are given — otherwise a caller that scopes by user but omits filters would fall\n\t\t\t// through to the unscoped fetch below and get every issue in the project instead of the user's.\n\t\t\tconst filters = options.filters?.length ? options.filters : [IssueFilter.Assignee];\n\t\t\tconst settled = await Promise.allSettled(\n\t\t\t\tfilters.map(filter => getSearchedUserIssuesForFilter(user, filter)),\n\t\t\t);\n\n\t\t\t// If every filter branch rejected, the read failed outright — propagate the first rejection instead\n\t\t\t// of returning an empty list, which the facade (getIssuesForProjectResult → runCaptured) would\n\t\t\t// otherwise surface as a successful \"no issues\" rather than a warning + fetchFailed. The first\n\t\t\t// reason is re-thrown as-is (not wrapped in an AggregateError) so the facade can still classify it\n\t\t\t// by type (auth/rate-limit) — wrapping would collapse every failure to a generic 'other'. The\n\t\t\t// remaining reasons would otherwise be discarded, so log them here to keep them diagnosable.\n\t\t\tif (settled.every(r => r.status === 'rejected')) {\n\t\t\t\tfor (let i = 1; i < settled.length; i++) {\n\t\t\t\t\tconst outcome = settled[i];\n\t\t\t\t\tif (outcome.status === 'rejected') {\n\t\t\t\t\t\tLogger.error(\n\t\t\t\t\t\t\toutcome.reason,\n\t\t\t\t\t\t\t`getProviderIssuesForProjectWithTruncation: filter '${filters[i]}' failed`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthrow settled[0].status === 'rejected' ? settled[0].reason : new Error('Jira issue read failed');\n\t\t\t}\n\n\t\t\tlet truncated = false;\n\t\t\tlet metadata: CollectionMetadata | undefined;\n\t\t\tconst resultsById = new Map<string, IssueShape>();\n\t\t\tfor (let i = 0; i < settled.length; i++) {\n\t\t\t\tconst outcome = settled[i];\n\t\t\t\tconst filter = filters[i];\n\t\t\t\t// A rejected filter branch (with at least one sibling succeeding) means this project's issues are\n\t\t\t\t// incomplete: keep the sibling results but record a structured failure so the facade can warn on\n\t\t\t\t// the specific filter (auth/rate-limit) instead of just a generic truncation flag.\n\t\t\t\tif (outcome.status !== 'fulfilled') {\n\t\t\t\t\t// Identical for every filter branch, so degrading it would report one failure per branch for a\n\t\t\t\t\t// single invalid call — see `throwIfCallerContractError`.\n\t\t\t\t\tthrowIfCallerContractError(outcome.reason);\n\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\tconst failure = toCollectionScopeFailure(\n\t\t\t\t\t\t{ providerId: this.id, resourceId: project.resourceId, projectId: project.name },\n\t\t\t\t\t\toutcome.reason,\n\t\t\t\t\t);\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, {\n\t\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\t\tfailures: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...failure,\n\t\t\t\t\t\t\t\tmessage: `Issue filter '${filter}' could not be read${\n\t\t\t\t\t\t\t\t\tfailure.message != null ? `: ${failure.message}` : ''\n\t\t\t\t\t\t\t\t}`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (outcome.value.status !== 'complete') {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t}\n\t\t\t\tif (outcome.value.metadata != null) {\n\t\t\t\t\tmetadata = mergeCollectionMetadata(metadata, outcome.value.metadata);\n\t\t\t\t}\n\t\t\t\tfor (const resultIssue of outcome.value.issues) {\n\t\t\t\t\tif (!resultsById.has(resultIssue.id)) {\n\t\t\t\t\t\tresultsById.set(resultIssue.id, resultIssue);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn truncated\n\t\t\t\t? { values: [...resultsById.values()], truncated: true, recovery: 'none', metadata: metadata }\n\t\t\t\t: { values: [...resultsById.values()], truncated: false, metadata: metadata };\n\t\t}\n\n\t\tconst unscoped = await drainIssues({});\n\t\tconst values = unscoped.issues\n\t\t\t.map(issue => toIssueShape(issue, this))\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\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraOrganizationDescriptor[],\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 * Overridden so `state` reaches the per-site read, which the default drops. Still reports `truncated: false`,\n\t * as the default did: the per-site loop has no way to say it stopped early.\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: JiraOrganizationDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\tconst myResources = resources ?? (await this.getProviderResourcesForUser(session));\n\t\tif (!myResources) return undefined;\n\n\t\tconst api = await this.getProvidersApi();\n\n\t\tconst results: IssueShape[] = [];\n\t\tfor (const resource of myResources) {\n\t\t\tif (cancellation?.aborted) break;\n\n\t\t\ttry {\n\t\t\t\tlet cursor = undefined;\n\t\t\t\tlet hasMore = false;\n\t\t\t\tlet requestCount = 0;\n\t\t\t\tdo {\n\t\t\t\t\tif (cancellation?.aborted) break;\n\n\t\t\t\t\tconst resourceIssues = await api.getIssuesForResourceForCurrentUser(\n\t\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\t\tresource.id,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcursor: cursor,\n\t\t\t\t\t\t\tstates: states,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\trequestCount += 1;\n\t\t\t\t\thasMore = resourceIssues.paging?.more ?? false;\n\t\t\t\t\tcursor = resourceIssues.paging?.cursor;\n\t\t\t\t\tconst formattedIssues = resourceIssues.values\n\t\t\t\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t\t\t\t.filter((result): result is IssueShape => result != null);\n\t\t\t\t\tif (formattedIssues.length > 0) {\n\t\t\t\t\t\tresults.push(...formattedIssues);\n\t\t\t\t\t}\n\t\t\t\t} while (requestCount < maxPagesPerRequest && hasMore);\n\t\t\t} catch (ex) {\n\t\t\t\t// TODO: We need a better way to message the failure to the user here.\n\t\t\t\t// This is a stopgap to prevent one bag org from throwing and preventing any issues from being returned.\n\t\t\t\tLogger.error(ex, 'searchProviderMyIssues');\n\t\t\t}\n\t\t}\n\n\t\treturn { values: results, truncated: false };\n\t}\n\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\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.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: key,\n\t\t});\n\t\treturn issue != null ? toIssueShape(issue, this) : undefined;\n\t}\n\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: JiraOrganizationDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: id,\n\t\t});\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\tprotected override async getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresourceId: string,\n\t\tid: string,\n\t\tresourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\tif (resourceUrl == null) {\n\t\t\tthrow new Error('Jira direct issue reads require a resource URL');\n\t\t}\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst apiResult = await api.getJiraIssueByKey(toTokenWithInfo(this.id, session), resourceId, resourceUrl, id);\n\t\tconst issue = apiResult != null ? toIssueShape(apiResult, this, { reliableStateCategory: true }) : undefined;\n\t\treturn issue != null ? { ...issue, type: 'issue' } : undefined;\n\t}\n\n\tprotected override async providerOnConnect(): Promise<void> {\n\t\tthis._autolinks.clear();\n\t\tif (this._session == null) return;\n\n\t\tconst session = this._session;\n\t\tconst { accessToken } = session;\n\t\tconst organizationsGeneration = this._organizations.generation;\n\t\tconst projectsGeneration = this._projects.generation;\n\n\t\t// Persisted discovery seeds the caches only while it would still be fresh in them: read within the TTL, and\n\t\t// after the caches were last dropped. Otherwise a re-sync, which runs this again, would put back the very\n\t\t// project list it just dropped, and a restart would trust one read any time before (#5907).\n\t\tconst isFresh = (timestamp: unknown): timestamp is number =>\n\t\t\ttypeof timestamp === 'number' &&\n\t\t\ttimestamp > this._discoveryInvalidatedAt &&\n\t\t\tDate.now() - timestamp < discoveryCacheTtl;\n\n\t\tconst storedOrganizations = this.ctx.storage.get(`jira:${accessToken}:organizations`);\n\t\t// Projects persisted without their sites belong to a session whose sites were cleared below; ignore them.\n\t\tconst storedProjects =\n\t\t\tstoredOrganizations != null ? this.ctx.storage.get(`jira:${accessToken}:projects`) : undefined;\n\n\t\tlet organizations: JiraOrganizationDescriptor[] | undefined;\n\t\tif (storedOrganizations?.data != null && isFresh(storedOrganizations.timestamp)) {\n\t\t\tconst seeded = storedOrganizations.data.map((o: JiraOrganizationDescriptor) => ({ ...o }));\n\t\t\torganizations = seeded;\n\t\t\tthis._organizations.set(accessToken, seeded, {\n\t\t\t\tgeneration: organizationsGeneration,\n\t\t\t\tstoredAt: storedOrganizations.timestamp,\n\t\t\t});\n\t\t} else {\n\t\t\t// Stamped with when the read started, so the TTL counts from what it actually saw.\n\t\t\tconst readAt = Date.now();\n\t\t\torganizations = await this.getProviderResourcesForUser(session, true);\n\t\t\tif (storedOrganizations == null) {\n\t\t\t\t// Clear all other stored organizations and projects when our session changes\n\t\t\t\tawait this.ctx.storage.deleteWithPrefix('jira');\n\t\t\t}\n\t\t\t// Not persisted when the caches were dropped while it was in flight: it is then a read from before the\n\t\t\t// refresh, and the next start, which knows nothing of that refresh, would take it for fresh.\n\t\t\tif (this._organizations.generation === organizationsGeneration) {\n\t\t\t\tawait this.ctx.storage.store(`jira:${accessToken}:organizations`, {\n\t\t\t\t\tv: 1,\n\t\t\t\t\ttimestamp: readAt,\n\t\t\t\t\tdata: organizations,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (storedProjects?.data != null && isFresh(storedProjects.timestamp)) {\n\t\t\tconst projectsByResource = new Map<string, JiraProjectDescriptor[]>();\n\t\t\tfor (const project of storedProjects.data as JiraProjectDescriptor[]) {\n\t\t\t\tconst projects = projectsByResource.get(project.resourceId);\n\t\t\t\tif (projects == null) {\n\t\t\t\t\tprojectsByResource.set(project.resourceId, [{ ...project }]);\n\t\t\t\t} else if (!projects.some(p => p.id === project.id)) {\n\t\t\t\t\tprojects.push({ ...project });\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const [resourceId, projects] of projectsByResource) {\n\t\t\t\tthis._projects.set(`${accessToken}:${resourceId}`, projects, {\n\t\t\t\t\tgeneration: projectsGeneration,\n\t\t\t\t\tstoredAt: storedProjects.timestamp,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (organizations?.length) {\n\t\t\tconst readAt = Date.now();\n\t\t\tconst projects = await this.getProviderProjectsForResources(session, organizations, true);\n\t\t\t// See the organizations above.\n\t\t\tif (this._projects.generation === projectsGeneration) {\n\t\t\t\tawait this.ctx.storage.store(`jira:${accessToken}:projects`, {\n\t\t\t\t\tv: 1,\n\t\t\t\t\ttimestamp: readAt,\n\t\t\t\t\tdata: projects,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\toverride invalidateDiscoveryCaches(): void {\n\t\tthis._discoveryInvalidatedAt = Date.now();\n\t\tthis._organizations.clear();\n\t\tthis._projects.clear();\n\t\tthis._autolinks.clear();\n\t}\n\n\tprotected override providerOnDisconnect(): void {\n\t\tthis._organizations.clear();\n\t\tthis._projects.clear();\n\t\tthis._autolinks.clear();\n\t}\n}\n"],"mappings":"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"}
@@ -18,16 +18,27 @@ export interface LinearOrganizationDescriptor extends IssueResourceDescriptor {
18
18
  }
19
19
  export interface LinearProjectDescriptor extends IssueResourceDescriptor {}
20
20
  export declare class LinearIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Linear> {
21
- private _autolinks;
21
+ /**
22
+ * Built from the teams, so it follows them: rebuilt when the team list changes or the caches are dropped, and once
23
+ * older than {@link discoveryCacheTtl}. An expired set is still served while it is rebuilt in the background, because
24
+ * autolinks are read on every render of a commit message and must not wait on the network (#5907).
25
+ */
26
+ private readonly _autolinks;
27
+ private readonly _autolinksBuilds;
22
28
  autolinks(): Promise<(AutolinkReference | DynamicAutolinkReference)[]>;
29
+ /** One build per token at a time: concurrent renders share it rather than each re-reading the teams. */
30
+ private buildAutolinks;
31
+ private buildAutolinksCore;
23
32
  private _organizations;
24
33
  private getOrganization;
25
- private _teams;
34
+ /** Expires, and is dropped on a re-sync, so a team created or left mid-session is picked up (#5907). */
35
+ private readonly _teams;
26
36
  private getTeams;
27
37
  private getTeamsWithMetadata;
28
38
  protected getProviderResourcesForUser(session: ProviderAuthenticationSession): Promise<ResourceDescriptor[] | undefined>;
29
39
  protected getProviderProjectsForResources(session: ProviderAuthenticationSession, _resources: ResourceDescriptor[]): Promise<ResourceDescriptor[] | undefined>;
30
40
  protected getProviderProjectsForResourcesWithMetadata(session: ProviderAuthenticationSession, _resources: ResourceDescriptor[]): Promise<ProviderApiCollectionResult<ResourceDescriptor>>;
41
+ invalidateDiscoveryCaches(): void;
31
42
  readonly authProvider: IntegrationAuthenticationProviderDescriptor;
32
43
  protected getProviderAccountForResource(session: ProviderAuthenticationSession, _resource: ResourceDescriptor): Promise<Account | undefined>;
33
44
  protected getProviderIssuesForProject(session: ProviderAuthenticationSession, project: ResourceDescriptor, options?: IssuesForProjectOptions): Promise<IssueShape[] | undefined>;