@gitkraken/core-gitlens 0.9.3 → 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.
- package/CHANGELOG.md +13 -1
- package/dist/plus/integrations/integrationService.d.ts +6 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +6 -1
- package/dist/plus/integrations/models/issueReads.d.ts +14 -0
- package/dist/plus/integrations/providers/bitbucket/models.d.ts +5 -5
- package/dist/plus/integrations/providers/bitbucket/models.js +2 -2
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
- package/dist/plus/integrations/providers/jira-server.d.ts +2 -2
- package/dist/plus/integrations/providers/jira-server.js +9 -3
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.d.ts +6 -1
- package/dist/plus/integrations/providers/jira.js +19 -3
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/linear.js +8 -2
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +19 -1
- package/dist/plus/integrations/providers/models.js +14 -5
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +8 -1
- package/dist/plus/integrations/providers/providersApi.js +3 -0
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/trello.js +1 -0
- package/dist/plus/integrations/providers/trello.js.map +1 -1
- package/dist/plus/integrations/reads/cursors.d.ts +8 -0
- package/dist/plus/integrations/reads/cursors.js +5 -3
- package/dist/plus/integrations/reads/cursors.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +0 -1
- package/dist/plus/integrations/reads/filters.d.ts +8 -0
- package/dist/plus/integrations/reads/filters.js +2 -1
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts +9 -1
- package/dist/plus/integrations/reads/issueTracker.js +20 -3
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/docs/integrations.md +4 -0
- package/package.json +2 -2
|
@@ -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 { 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 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 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\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.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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"}
|
|
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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"}
|
|
@@ -5,7 +5,7 @@ import "../constants.js";
|
|
|
5
5
|
import { toCollectionScopeFailure } from "../collectionMetadata.js";
|
|
6
6
|
import { toTokenWithInfo } from "../authentication/models.js";
|
|
7
7
|
import { mergeCollectionMetadata } from "./utils/providerPaging.js";
|
|
8
|
-
import { fromProviderIssue, providersMetadata, toIssueShape } from "./models.js";
|
|
8
|
+
import { fromProviderIssue, providersMetadata, toIssueShape, toProviderIssueStates } from "./models.js";
|
|
9
9
|
import { IssuesIntegration } from "../models/issuesIntegration.js";
|
|
10
10
|
import { DiscoveryCache } from "./utils/discoveryCache.js";
|
|
11
11
|
import * as l10n from "@vscode/l10n";
|
|
@@ -169,6 +169,7 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
169
169
|
async getProviderIssuesForProjectWithTruncation(session, project, options) {
|
|
170
170
|
if (!isIssueResourceDescriptor(project)) return void 0;
|
|
171
171
|
const api = await this.getProvidersApi();
|
|
172
|
+
const states = toProviderIssueStates(options?.state);
|
|
172
173
|
let cursor;
|
|
173
174
|
let hasMore;
|
|
174
175
|
let requestCount = 0;
|
|
@@ -178,7 +179,10 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
178
179
|
do {
|
|
179
180
|
let result;
|
|
180
181
|
try {
|
|
181
|
-
result = await api.getLinearIssues(toTokenWithInfo(this.id, session), {
|
|
182
|
+
result = await api.getLinearIssues(toTokenWithInfo(this.id, session), {
|
|
183
|
+
teams: [project.id],
|
|
184
|
+
states
|
|
185
|
+
}, {
|
|
182
186
|
cursor,
|
|
183
187
|
sort: options?.sort
|
|
184
188
|
});
|
|
@@ -273,6 +277,7 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
273
277
|
async searchProviderMyIssuesWithTruncation(session, resources, cancellation, options) {
|
|
274
278
|
if (resources != null) return;
|
|
275
279
|
const api = await this.getProvidersApi();
|
|
280
|
+
const states = toProviderIssueStates(options?.state);
|
|
276
281
|
let cursor = void 0;
|
|
277
282
|
let hasMore = false;
|
|
278
283
|
let requestCount = 0;
|
|
@@ -283,6 +288,7 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
283
288
|
if (cancellation?.aborted) break;
|
|
284
289
|
const result = await api.getIssuesForCurrentUser(toTokenWithInfo(this.id, session), {
|
|
285
290
|
cursor,
|
|
291
|
+
states,
|
|
286
292
|
sort: options?.sort
|
|
287
293
|
});
|
|
288
294
|
requestCount += 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linear.js","names":[],"sources":["../../../../../plus/integrations/src/providers/linear.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, IssueOrPullRequestType } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor, ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isIssueResourceDescriptor } from '@gitlens/git/utils/resourceDescriptor.utils.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 { toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport { IntegrationReadUnavailableError } from '../errors.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 { fromProviderIssue, providersMetadata, toIssueShape } from './models.js';\nimport { DiscoveryCache, discoveryCacheTtl } from './utils/discoveryCache.js';\nimport { mergeCollectionMetadata } from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Linear];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n/**\n * The account-wide drain's own backstop, separate from {@link maxPagesPerRequest}.\n *\n * Raised to 50 (5,000 issues at the SDK's 100-per-page) because 10 no longer means what it used to. That read\n * once fanned out over four overlapping relationship queries and merged them, so most of what a page cost was\n * rows already returned — a low budget capped the waste. It is now a single server-ordered query over an `or`\n * filter, so every page is 100 issues the caller has not seen, and the same budget just truncates real results\n * at 1,000.\n *\n * Reaching it is reported rather than silent: {@link LinearIntegration.searchProviderMyIssuesWithTruncation}\n * returns `truncated`, so the facade can say the read was incomplete instead of publishing a capped list as a\n * whole account. The bound stays — a runaway cursor must not spend requests forever — but it is now set where a\n * real Linear account is unlikely to reach it rather than where the duplication used to hurt.\n */\nconst maxAccountWidePagesPerRequest = 50;\nconst linearImplicitTeamsPageSize = 50;\n\nexport interface LinearTeamDescriptor extends IssueResourceDescriptor {\n\tavatarUrl: string | undefined;\n}\n\nexport interface LinearOrganizationDescriptor extends IssueResourceDescriptor {\n\turl: string;\n}\n\nexport interface LinearProjectDescriptor extends IssueResourceDescriptor {}\n\nexport class LinearIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Linear> {\n\t/**\n\t * Built from the teams, so it follows them: rebuilt when the team list changes or the caches are dropped, and once\n\t * older than {@link discoveryCacheTtl}. An expired set is still served while it is rebuilt in the background, because\n\t * autolinks are read on every render of a commit message and must not wait on the network (#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) {\n\t\t\treturn [];\n\t\t}\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).catch(() => {});\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 the teams. */\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 generation = this._teams.generation;\n\t\tconst organization = await this.getOrganization(session);\n\t\tif (organization == null) return [];\n\n\t\tconst autolinks: (AutolinkReference | DynamicAutolinkReference)[] = [];\n\n\t\tconst teams = await this.getTeams(session);\n\t\tfor (const team of teams ?? []) {\n\t\t\tconst dashedPrefix = `${team.key}-`;\n\t\t\tconst underscoredPrefix = `${team.key}_`;\n\n\t\t\tautolinks.push({\n\t\t\t\tprefix: dashedPrefix,\n\t\t\t\turl: `${organization.url}/issue/${dashedPrefix}<num>`,\n\t\t\t\talphanumeric: false,\n\t\t\t\tignoreCase: false,\n\t\t\t\ttitle: l10n.t('Open Issue {0} on {1}', `${dashedPrefix}<num>`, organization.name),\n\n\t\t\t\ttype: 'issue',\n\t\t\t\tdescription: l10n.t('{0} Issue {1}', organization.name, `${dashedPrefix}<num>`),\n\t\t\t\tdescriptor: { ...organization },\n\t\t\t});\n\t\t\tautolinks.push({\n\t\t\t\tprefix: underscoredPrefix,\n\t\t\t\turl: `${organization.url}/issue/${dashedPrefix}<num>`,\n\t\t\t\talphanumeric: false,\n\t\t\t\tignoreCase: false,\n\t\t\t\treferenceType: 'branch',\n\t\t\t\ttitle: l10n.t('Open Issue {0} on {1}', `${dashedPrefix}<num>`, organization.name),\n\n\t\t\t\ttype: 'issue',\n\t\t\t\tdescription: l10n.t('{0} Issue {1}', organization.name, `${dashedPrefix}<num>`),\n\t\t\t\tdescriptor: { ...organization },\n\t\t\t});\n\t\t}\n\n\t\t// Not kept when built from teams read before the caches were dropped.\n\t\tif (this._teams.generation === generation) {\n\t\t\tthis._autolinks.set(session.accessToken, { autolinks: autolinks, builtAt: Date.now() });\n\t\t}\n\t\treturn autolinks;\n\t}\n\n\tprivate _organizations: Map<string, LinearOrganizationDescriptor | undefined> | undefined;\n\tprivate async getOrganization(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<LinearOrganizationDescriptor | undefined> {\n\t\tconst { accessToken } = session;\n\t\tthis._organizations ??= new Map<string, LinearOrganizationDescriptor | 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 organization = await api.getLinearOrganization(toTokenWithInfo(this.id, session));\n\t\t\tconst descriptor: LinearOrganizationDescriptor | undefined = organization && {\n\t\t\t\tid: organization.id,\n\t\t\t\tkey: organization.key,\n\t\t\t\tname: organization.name,\n\t\t\t\turl: organization.url,\n\t\t\t};\n\t\t\tif (descriptor) {\n\t\t\t\tthis._organizations.set(accessToken, descriptor);\n\t\t\t}\n\t\t}\n\n\t\treturn this._organizations.get(accessToken);\n\t}\n\n\t/** Expires, and is dropped on a re-sync, so a team created or left mid-session is picked up (#5907). */\n\tprivate readonly _teams = new DiscoveryCache<LinearTeamDescriptor[]>();\n\tprivate async getTeams(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<LinearTeamDescriptor[] | undefined> {\n\t\treturn (await this.getTeamsWithMetadata(session, force))?.values;\n\t}\n\n\tprivate async getTeamsWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<ProviderApiCollectionResult<LinearTeamDescriptor> | undefined> {\n\t\tconst { accessToken } = session;\n\n\t\tconst cachedResources = this._teams.get(accessToken);\n\t\tif (cachedResources != null && !force) return { values: cachedResources };\n\n\t\tconst generation = this._teams.generation;\n\t\tconst api = await this.getProvidersApi();\n\t\tconst teams = await api.getLinearTeamsForCurrentUser(toTokenWithInfo(this.id, session));\n\t\tconst descriptors: LinearTeamDescriptor[] | undefined = teams?.map(t => ({\n\t\t\tid: t.id,\n\t\t\tkey: t.key,\n\t\t\tname: t.name,\n\t\t\tavatarUrl: t.iconUrl,\n\t\t}));\n\t\tif (descriptors == null) return undefined;\n\n\t\t// provider-apis currently requests Linear's teams connection without pageInfo or a cursor. Linear's\n\t\t// implicit page size is 50, so exactly a full page cannot prove there is no team 51. Preserve the useful\n\t\t// prefix, but mark it unknown and leave it uncached so the next read retries instead of treating it as\n\t\t// authoritative. Once provider-apis exposes a paged primitive, replace this fail-closed guard with a drain.\n\t\tif (descriptors.length >= linearImplicitTeamsPageSize) {\n\t\t\treturn { values: descriptors, metadata: { completeness: 'unknown' } };\n\t\t}\n\n\t\tif (this._teams.set(accessToken, descriptors, { generation: generation })) {\n\t\t\t// Built from the teams; a new set means new prefixes.\n\t\t\tthis._autolinks.delete(accessToken);\n\t\t}\n\t\treturn { values: descriptors };\n\t}\n\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<ResourceDescriptor[] | undefined> {\n\t\tconst organization = await this.getOrganization(session);\n\t\treturn organization != null ? [organization] : undefined;\n\t}\n\tprotected override getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources: ResourceDescriptor[],\n\t): Promise<ResourceDescriptor[] | undefined> {\n\t\treturn this.getTeams(session);\n\t}\n\tprotected override async getProviderProjectsForResourcesWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources: ResourceDescriptor[],\n\t): Promise<ProviderApiCollectionResult<ResourceDescriptor>> {\n\t\treturn (await this.getTeamsWithMetadata(session)) ?? { values: [] };\n\t}\n\n\toverride invalidateDiscoveryCaches(): void {\n\t\tthis._organizations = undefined;\n\t\tthis._teams.clear();\n\t\tthis._autolinks.clear();\n\t}\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// Linear's viewer isn't a ProviderAccount (no username/avatar), so build the Account manually\n\t\t// (Trello-style) from the fields the viewer query returns.\n\t\tconst user = await api.getLinearCurrentUser(toTokenWithInfo(this.id, session));\n\t\tif (user == null) return undefined;\n\n\t\treturn {\n\t\t\tprovider: this,\n\t\t\tid: user.id,\n\t\t\tname: user.name ?? user.displayName ?? undefined,\n\t\t\tusername: user.displayName ?? undefined,\n\t\t\temail: user.email ?? undefined,\n\t\t\tavatarUrl: undefined,\n\t\t};\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\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: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tif (!isIssueResourceDescriptor(project)) return undefined;\n\n\t\tconst api = await this.getProvidersApi();\n\t\t// `getProviderProjectsForResources` returns Linear teams, so `project.id` is a team id here. Drain the\n\t\t// team's issues (Linear pages by cursor); bounded by maxPagesPerRequest as a backstop. `truncated` is\n\t\t// set when that backstop stopped the drain with more pages still available.\n\t\tlet cursor: string | undefined;\n\t\tlet hasMore: boolean;\n\t\tlet requestCount = 0;\n\t\tlet truncated = false;\n\t\tlet collectionMetadata: CollectionMetadata | undefined;\n\t\tconst issues: IssueShape[] = [];\n\t\tdo {\n\t\t\tlet result: Awaited<ReturnType<typeof api.getLinearIssues>>;\n\t\t\ttry {\n\t\t\t\tresult = await api.getLinearIssues(\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\t{ teams: [project.id] },\n\t\t\t\t\t{ cursor: cursor, sort: options?.sort },\n\t\t\t\t);\n\t\t\t} catch (ex) {\n\t\t\t\t// A page failure after the first page leaves the already-drained prefix intact; record the\n\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// fetched yet, preserve the original throw behavior so the caller sees a hard error.\n\t\t\t\tif (issues.length === 0) throw ex;\n\n\t\t\t\ttruncated = true;\n\t\t\t\tcollectionMetadata = mergeCollectionMetadata(collectionMetadata, {\n\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\tfailures: [toCollectionScopeFailure({ providerId: this.id, projectId: project.id }, ex)],\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\trequestCount += 1;\n\t\t\thasMore = result.paging?.more ?? false;\n\t\t\tconst nextCursor = result.paging?.cursor;\n\t\t\ttruncated ||= result.paging?.truncated === true;\n\t\t\tcollectionMetadata = mergeCollectionMetadata(collectionMetadata, result.metadata);\n\t\t\tfor (const issue of result.values) {\n\t\t\t\tconst shape = toIssueShape(issue, this);\n\t\t\t\tif (shape != null) {\n\t\t\t\t\tissues.push(shape);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// The provider claims more but returns no advancing cursor: we can't continue without re-reading the\n\t\t\t// same page, so the drain is incomplete — flag it rather than silently stopping.\n\t\t\tif (hasMore && (nextCursor == null || nextCursor === cursor)) {\n\t\t\t\ttruncated = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcursor = nextCursor;\n\t\t\tif (hasMore && requestCount >= maxPagesPerRequest) {\n\t\t\t\ttruncated = true;\n\t\t\t}\n\t\t} while (requestCount < maxPagesPerRequest && hasMore);\n\n\t\t// Linear's issue list has no server-side author/assignee filter, so scope to the current user\n\t\t// client-side when a user was requested (the assignee filter is what \"my issues\" means here).\n\t\t// Match on the viewer's stable id, not the passed display name: Linear's `name` (full name) and\n\t\t// `displayName` (nickname) are distinct fields, and assignees are normalized with `.name` = the full\n\t\t// name while the caller's `user` is the displayName — so a name string can miss. The assignee `.id`\n\t\t// is the Linear user id, which is unambiguous.\n\t\tif (options?.user != null) {\n\t\t\tconst viewerId = (await api.getLinearCurrentUser(toTokenWithInfo(this.id, session)))?.id;\n\t\t\t// If the viewer can't be resolved we can't scope to \"my issues\" — returning the unfiltered team\n\t\t\t// issues would leak everyone else's, and returning [] is indistinguishable from \"no issues assigned\n\t\t\t// to me\". Throw so the facade (getIssuesForProjectResult → runCaptured) surfaces a warning +\n\t\t\t// fetchFailed the caller can act on, instead of a silent empty.\n\t\t\tif (viewerId == null) {\n\t\t\t\tthrow new IntegrationReadUnavailableError(\n\t\t\t\t\tmetadata.name,\n\t\t\t\t\t'could not resolve the current user to scope issues to',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst values = issues.filter(issue => issue.assignees?.some(a => a.id === viewerId));\n\t\t\treturn truncated\n\t\t\t\t? { values: values, truncated: true, recovery: 'none', metadata: collectionMetadata }\n\t\t\t\t: { values: values, truncated: false, metadata: collectionMetadata };\n\t\t}\n\n\t\treturn truncated\n\t\t\t? { values: issues, truncated: true, recovery: 'none', metadata: collectionMetadata }\n\t\t\t: { values: issues, truncated: false, metadata: collectionMetadata };\n\t}\n\n\toverride get id(): IssuesCloudHostIntegrationId.Linear {\n\t\treturn IssuesCloudHostIntegrationId.Linear;\n\t}\n\tprotected override get key(): 'linear' {\n\t\treturn 'linear';\n\t}\n\toverride get name(): string {\n\t\treturn metadata.name;\n\t}\n\toverride get domain(): string {\n\t\treturn metadata.domain;\n\t}\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\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 Linear, drained to exhaustion and reporting whether it got there.\n\t *\n\t * The SDK read is one server-ordered query over an `or` filter across the four relationships (assigned,\n\t * created, named in an issue's content, named in a comment), so each page is 100 issues the caller has not\n\t * seen and the pages are ordered as one sequence. Draining it is therefore just following the cursor.\n\t *\n\t * Overridden rather than left to the base default because that default hardcodes `truncated: false`, and a\n\t * bounded drain that stops early is exactly the case a caller must be able to see. Three things end the\n\t * drain, and only one of them is completion:\n\t *\n\t * - the provider says there is no next page — complete;\n\t * - the backstop is reached — `truncated`, because issues beyond it exist and were not read;\n\t * - the provider claims a next page but hands back a cursor that does not advance — also `truncated`, since\n\t * the read cannot continue and the remainder is unreachable rather than absent.\n\t *\n\t * Cancellation is deliberately NOT truncation: the caller asked for the read to stop, so the partial list is\n\t * what it asked for, and flagging it would surface an incompleteness warning for a user action.\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tif (resources != null) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst api = await this.getProvidersApi();\n\t\tlet cursor = undefined;\n\t\t// Starts false so an immediate cancellation, which leaves the loop before the first response, reads as\n\t\t// \"no more pages known\" rather than as an unfinished drain.\n\t\tlet hasMore = false;\n\t\tlet requestCount = 0;\n\t\tlet truncated = false;\n\t\tconst issues = [];\n\t\ttry {\n\t\t\tdo {\n\t\t\t\tif (cancellation?.aborted) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst result = await api.getIssuesForCurrentUser(toTokenWithInfo(this.id, session), {\n\t\t\t\t\tcursor: cursor,\n\t\t\t\t\tsort: options?.sort,\n\t\t\t\t});\n\t\t\t\trequestCount += 1;\n\t\t\t\thasMore = result.paging?.more ?? false;\n\t\t\t\tconst nextCursor = result.paging?.cursor;\n\n\t\t\t\t// Keep this page before deciding whether to continue: the request is already paid for, so\n\t\t\t\t// dropping its rows would lose real results to save nothing.\n\t\t\t\tconst formattedIssues = result.values\n\t\t\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t\t\t.filter((result): result is IssueShape => result != null);\n\t\t\t\tif (formattedIssues.length > 0) {\n\t\t\t\t\tissues.push(...formattedIssues);\n\t\t\t\t}\n\n\t\t\t\t// The provider claims more but hands back no advancing cursor: continuing would re-read the page\n\t\t\t\t// just returned. `ProvidersApi.getPagedResult` already normalizes this into `more: false`, so\n\t\t\t\t// this is a backstop for if that ever stops holding rather than a case seen today -- but the rest\n\t\t\t\t// of the account is unreachable either way, which is what `truncated` says.\n\t\t\t\tif (hasMore && (nextCursor == null || nextCursor === cursor)) {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcursor = nextCursor;\n\t\t\t} while (requestCount < maxAccountWidePagesPerRequest && hasMore);\n\n\t\t\t// Ran out of budget with pages still to come.\n\t\t\tif (hasMore && requestCount >= maxAccountWidePagesPerRequest) {\n\t\t\t\ttruncated = true;\n\t\t\t}\n\t\t} catch (ex) {\n\t\t\tif (issues.length === 0) {\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\t// Kept what was already fetched, so the list is real but short of the account.\n\t\t\ttruncated = true;\n\t\t\tLogger.error(ex, 'searchProviderMyIssues');\n\t\t}\n\t\treturn { values: issues, truncated: truncated };\n\t}\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\t{ key }: { id: string; key: string },\n\t\t_type: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst issue = await this.getRawProviderIssue(session, resource, key);\n\t\tconst autolinkableIssue: ProviderIssue | undefined = issue && {\n\t\t\t...issue,\n\t\t\turl: this.getIssueAutolinkLikeUrl(issue),\n\t\t};\n\t\treturn autolinkableIssue && toIssueShape(autolinkableIssue, this);\n\t}\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst result = await this.getRawProviderIssue(session, resource, id);\n\t\treturn result && fromProviderIssue(result, this);\n\t}\n\n\tprotected override async getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresourceId: string,\n\t\tid: string,\n\t\t_resourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst result = await api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resourceId,\n\t\t\tnumber: id,\n\t\t});\n\t\treturn result && fromProviderIssue(result, this);\n\t}\n\n\tprivate async getRawProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t): Promise<ProviderIssue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tif (!isIssueResourceDescriptor(resource)) {\n\t\t\tLogger.error(undefined, 'getProviderIssue: resource is not an IssueResourceDescriptor');\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: id,\n\t\t});\n\t}\n\tprivate getIssueAutolinkLikeUrl(issue: ProviderIssue): string | null {\n\t\tconst url = issue.url;\n\t\tif (url == null) return null;\n\n\t\tconst lastSegment = url.split('/').pop();\n\t\tif (!lastSegment || issue.number === lastSegment) {\n\t\t\treturn url;\n\t\t}\n\t\treturn url.substring(0, url.length - lastSegment.length - 1);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"linear.js","names":[],"sources":["../../../../../plus/integrations/src/providers/linear.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, IssueOrPullRequestType } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { IssueResourceDescriptor, ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isIssueResourceDescriptor } from '@gitlens/git/utils/resourceDescriptor.utils.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 { toCollectionScopeFailure } from '../collectionMetadata.js';\nimport { IssuesCloudHostIntegrationId } from '../constants.js';\nimport { IntegrationReadUnavailableError } from '../errors.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 { fromProviderIssue, providersMetadata, toIssueShape, toProviderIssueStates } from './models.js';\nimport { DiscoveryCache, discoveryCacheTtl } from './utils/discoveryCache.js';\nimport { mergeCollectionMetadata } from './utils/providerPaging.js';\n\nconst metadata = providersMetadata[IssuesCloudHostIntegrationId.Linear];\nconst authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });\nconst maxPagesPerRequest = 10;\n/**\n * The account-wide drain's own backstop, separate from {@link maxPagesPerRequest}.\n *\n * Raised to 50 (5,000 issues at the SDK's 100-per-page) because 10 no longer means what it used to. That read\n * once fanned out over four overlapping relationship queries and merged them, so most of what a page cost was\n * rows already returned — a low budget capped the waste. It is now a single server-ordered query over an `or`\n * filter, so every page is 100 issues the caller has not seen, and the same budget just truncates real results\n * at 1,000.\n *\n * Reaching it is reported rather than silent: {@link LinearIntegration.searchProviderMyIssuesWithTruncation}\n * returns `truncated`, so the facade can say the read was incomplete instead of publishing a capped list as a\n * whole account. The bound stays — a runaway cursor must not spend requests forever — but it is now set where a\n * real Linear account is unlikely to reach it rather than where the duplication used to hurt.\n */\nconst maxAccountWidePagesPerRequest = 50;\nconst linearImplicitTeamsPageSize = 50;\n\nexport interface LinearTeamDescriptor extends IssueResourceDescriptor {\n\tavatarUrl: string | undefined;\n}\n\nexport interface LinearOrganizationDescriptor extends IssueResourceDescriptor {\n\turl: string;\n}\n\nexport interface LinearProjectDescriptor extends IssueResourceDescriptor {}\n\nexport class LinearIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Linear> {\n\t/**\n\t * Built from the teams, so it follows them: rebuilt when the team list changes or the caches are dropped, and once\n\t * older than {@link discoveryCacheTtl}. An expired set is still served while it is rebuilt in the background, because\n\t * autolinks are read on every render of a commit message and must not wait on the network (#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) {\n\t\t\treturn [];\n\t\t}\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).catch(() => {});\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 the teams. */\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 generation = this._teams.generation;\n\t\tconst organization = await this.getOrganization(session);\n\t\tif (organization == null) return [];\n\n\t\tconst autolinks: (AutolinkReference | DynamicAutolinkReference)[] = [];\n\n\t\tconst teams = await this.getTeams(session);\n\t\tfor (const team of teams ?? []) {\n\t\t\tconst dashedPrefix = `${team.key}-`;\n\t\t\tconst underscoredPrefix = `${team.key}_`;\n\n\t\t\tautolinks.push({\n\t\t\t\tprefix: dashedPrefix,\n\t\t\t\turl: `${organization.url}/issue/${dashedPrefix}<num>`,\n\t\t\t\talphanumeric: false,\n\t\t\t\tignoreCase: false,\n\t\t\t\ttitle: l10n.t('Open Issue {0} on {1}', `${dashedPrefix}<num>`, organization.name),\n\n\t\t\t\ttype: 'issue',\n\t\t\t\tdescription: l10n.t('{0} Issue {1}', organization.name, `${dashedPrefix}<num>`),\n\t\t\t\tdescriptor: { ...organization },\n\t\t\t});\n\t\t\tautolinks.push({\n\t\t\t\tprefix: underscoredPrefix,\n\t\t\t\turl: `${organization.url}/issue/${dashedPrefix}<num>`,\n\t\t\t\talphanumeric: false,\n\t\t\t\tignoreCase: false,\n\t\t\t\treferenceType: 'branch',\n\t\t\t\ttitle: l10n.t('Open Issue {0} on {1}', `${dashedPrefix}<num>`, organization.name),\n\n\t\t\t\ttype: 'issue',\n\t\t\t\tdescription: l10n.t('{0} Issue {1}', organization.name, `${dashedPrefix}<num>`),\n\t\t\t\tdescriptor: { ...organization },\n\t\t\t});\n\t\t}\n\n\t\t// Not kept when built from teams read before the caches were dropped.\n\t\tif (this._teams.generation === generation) {\n\t\t\tthis._autolinks.set(session.accessToken, { autolinks: autolinks, builtAt: Date.now() });\n\t\t}\n\t\treturn autolinks;\n\t}\n\n\tprivate _organizations: Map<string, LinearOrganizationDescriptor | undefined> | undefined;\n\tprivate async getOrganization(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<LinearOrganizationDescriptor | undefined> {\n\t\tconst { accessToken } = session;\n\t\tthis._organizations ??= new Map<string, LinearOrganizationDescriptor | 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 organization = await api.getLinearOrganization(toTokenWithInfo(this.id, session));\n\t\t\tconst descriptor: LinearOrganizationDescriptor | undefined = organization && {\n\t\t\t\tid: organization.id,\n\t\t\t\tkey: organization.key,\n\t\t\t\tname: organization.name,\n\t\t\t\turl: organization.url,\n\t\t\t};\n\t\t\tif (descriptor) {\n\t\t\t\tthis._organizations.set(accessToken, descriptor);\n\t\t\t}\n\t\t}\n\n\t\treturn this._organizations.get(accessToken);\n\t}\n\n\t/** Expires, and is dropped on a re-sync, so a team created or left mid-session is picked up (#5907). */\n\tprivate readonly _teams = new DiscoveryCache<LinearTeamDescriptor[]>();\n\tprivate async getTeams(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<LinearTeamDescriptor[] | undefined> {\n\t\treturn (await this.getTeamsWithMetadata(session, force))?.values;\n\t}\n\n\tprivate async getTeamsWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\tforce: boolean = false,\n\t): Promise<ProviderApiCollectionResult<LinearTeamDescriptor> | undefined> {\n\t\tconst { accessToken } = session;\n\n\t\tconst cachedResources = this._teams.get(accessToken);\n\t\tif (cachedResources != null && !force) return { values: cachedResources };\n\n\t\tconst generation = this._teams.generation;\n\t\tconst api = await this.getProvidersApi();\n\t\tconst teams = await api.getLinearTeamsForCurrentUser(toTokenWithInfo(this.id, session));\n\t\tconst descriptors: LinearTeamDescriptor[] | undefined = teams?.map(t => ({\n\t\t\tid: t.id,\n\t\t\tkey: t.key,\n\t\t\tname: t.name,\n\t\t\tavatarUrl: t.iconUrl,\n\t\t}));\n\t\tif (descriptors == null) return undefined;\n\n\t\t// provider-apis currently requests Linear's teams connection without pageInfo or a cursor. Linear's\n\t\t// implicit page size is 50, so exactly a full page cannot prove there is no team 51. Preserve the useful\n\t\t// prefix, but mark it unknown and leave it uncached so the next read retries instead of treating it as\n\t\t// authoritative. Once provider-apis exposes a paged primitive, replace this fail-closed guard with a drain.\n\t\tif (descriptors.length >= linearImplicitTeamsPageSize) {\n\t\t\treturn { values: descriptors, metadata: { completeness: 'unknown' } };\n\t\t}\n\n\t\tif (this._teams.set(accessToken, descriptors, { generation: generation })) {\n\t\t\t// Built from the teams; a new set means new prefixes.\n\t\t\tthis._autolinks.delete(accessToken);\n\t\t}\n\t\treturn { values: descriptors };\n\t}\n\n\tprotected override async getProviderResourcesForUser(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<ResourceDescriptor[] | undefined> {\n\t\tconst organization = await this.getOrganization(session);\n\t\treturn organization != null ? [organization] : undefined;\n\t}\n\tprotected override getProviderProjectsForResources(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources: ResourceDescriptor[],\n\t): Promise<ResourceDescriptor[] | undefined> {\n\t\treturn this.getTeams(session);\n\t}\n\tprotected override async getProviderProjectsForResourcesWithMetadata(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resources: ResourceDescriptor[],\n\t): Promise<ProviderApiCollectionResult<ResourceDescriptor>> {\n\t\treturn (await this.getTeamsWithMetadata(session)) ?? { values: [] };\n\t}\n\n\toverride invalidateDiscoveryCaches(): void {\n\t\tthis._organizations = undefined;\n\t\tthis._teams.clear();\n\t\tthis._autolinks.clear();\n\t}\n\treadonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;\n\n\tprotected override async getProviderAccountForResource(\n\t\tsession: ProviderAuthenticationSession,\n\t\t_resource: ResourceDescriptor,\n\t): Promise<Account | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\t// Linear's viewer isn't a ProviderAccount (no username/avatar), so build the Account manually\n\t\t// (Trello-style) from the fields the viewer query returns.\n\t\tconst user = await api.getLinearCurrentUser(toTokenWithInfo(this.id, session));\n\t\tif (user == null) return undefined;\n\n\t\treturn {\n\t\t\tprovider: this,\n\t\t\tid: user.id,\n\t\t\tname: user.name ?? user.displayName ?? undefined,\n\t\t\tusername: user.displayName ?? undefined,\n\t\t\temail: user.email ?? undefined,\n\t\t\tavatarUrl: undefined,\n\t\t};\n\t}\n\n\tprotected override async getProviderIssuesForProject(\n\t\tsession: ProviderAuthenticationSession,\n\t\tproject: ResourceDescriptor,\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: ResourceDescriptor,\n\t\toptions?: IssuesForProjectOptions,\n\t): Promise<ProjectIssuesDrain | undefined> {\n\t\tif (!isIssueResourceDescriptor(project)) return undefined;\n\n\t\tconst api = await this.getProvidersApi();\n\t\t// `getProviderProjectsForResources` returns Linear teams, so `project.id` is a team id here. Drain the\n\t\t// team's issues (Linear pages by cursor); bounded by maxPagesPerRequest as a backstop. `truncated` is\n\t\t// set when that backstop stopped the drain with more pages still available.\n\t\t//\n\t\t// The state is narrowed server-side, not after the drain: a team's completed and canceled issues would\n\t\t// otherwise count against the backstop, so a team with enough done work runs out of pages before its open\n\t\t// issues, and the viewer filter below would then hand back a page that looks complete.\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\tlet cursor: string | undefined;\n\t\tlet hasMore: boolean;\n\t\tlet requestCount = 0;\n\t\tlet truncated = false;\n\t\tlet collectionMetadata: CollectionMetadata | undefined;\n\t\tconst issues: IssueShape[] = [];\n\t\tdo {\n\t\t\tlet result: Awaited<ReturnType<typeof api.getLinearIssues>>;\n\t\t\ttry {\n\t\t\t\tresult = await api.getLinearIssues(\n\t\t\t\t\ttoTokenWithInfo(this.id, session),\n\t\t\t\t\t{ teams: [project.id], states: states },\n\t\t\t\t\t{ cursor: cursor, sort: options?.sort },\n\t\t\t\t);\n\t\t\t} catch (ex) {\n\t\t\t\t// A page failure after the first page leaves the already-drained prefix intact; record the\n\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// fetched yet, preserve the original throw behavior so the caller sees a hard error.\n\t\t\t\tif (issues.length === 0) throw ex;\n\n\t\t\t\ttruncated = true;\n\t\t\t\tcollectionMetadata = mergeCollectionMetadata(collectionMetadata, {\n\t\t\t\t\tcompleteness: 'partial',\n\t\t\t\t\tfailures: [toCollectionScopeFailure({ providerId: this.id, projectId: project.id }, ex)],\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\trequestCount += 1;\n\t\t\thasMore = result.paging?.more ?? false;\n\t\t\tconst nextCursor = result.paging?.cursor;\n\t\t\ttruncated ||= result.paging?.truncated === true;\n\t\t\tcollectionMetadata = mergeCollectionMetadata(collectionMetadata, result.metadata);\n\t\t\tfor (const issue of result.values) {\n\t\t\t\tconst shape = toIssueShape(issue, this);\n\t\t\t\tif (shape != null) {\n\t\t\t\t\tissues.push(shape);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// The provider claims more but returns no advancing cursor: we can't continue without re-reading the\n\t\t\t// same page, so the drain is incomplete — flag it rather than silently stopping.\n\t\t\tif (hasMore && (nextCursor == null || nextCursor === cursor)) {\n\t\t\t\ttruncated = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcursor = nextCursor;\n\t\t\tif (hasMore && requestCount >= maxPagesPerRequest) {\n\t\t\t\ttruncated = true;\n\t\t\t}\n\t\t} while (requestCount < maxPagesPerRequest && hasMore);\n\n\t\t// Linear's issue list has no server-side author/assignee filter, so scope to the current user\n\t\t// client-side when a user was requested (the assignee filter is what \"my issues\" means here).\n\t\t// Match on the viewer's stable id, not the passed display name: Linear's `name` (full name) and\n\t\t// `displayName` (nickname) are distinct fields, and assignees are normalized with `.name` = the full\n\t\t// name while the caller's `user` is the displayName — so a name string can miss. The assignee `.id`\n\t\t// is the Linear user id, which is unambiguous.\n\t\tif (options?.user != null) {\n\t\t\tconst viewerId = (await api.getLinearCurrentUser(toTokenWithInfo(this.id, session)))?.id;\n\t\t\t// If the viewer can't be resolved we can't scope to \"my issues\" — returning the unfiltered team\n\t\t\t// issues would leak everyone else's, and returning [] is indistinguishable from \"no issues assigned\n\t\t\t// to me\". Throw so the facade (getIssuesForProjectResult → runCaptured) surfaces a warning +\n\t\t\t// fetchFailed the caller can act on, instead of a silent empty.\n\t\t\tif (viewerId == null) {\n\t\t\t\tthrow new IntegrationReadUnavailableError(\n\t\t\t\t\tmetadata.name,\n\t\t\t\t\t'could not resolve the current user to scope issues to',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst values = issues.filter(issue => issue.assignees?.some(a => a.id === viewerId));\n\t\t\treturn truncated\n\t\t\t\t? { values: values, truncated: true, recovery: 'none', metadata: collectionMetadata }\n\t\t\t\t: { values: values, truncated: false, metadata: collectionMetadata };\n\t\t}\n\n\t\treturn truncated\n\t\t\t? { values: issues, truncated: true, recovery: 'none', metadata: collectionMetadata }\n\t\t\t: { values: issues, truncated: false, metadata: collectionMetadata };\n\t}\n\n\toverride get id(): IssuesCloudHostIntegrationId.Linear {\n\t\treturn IssuesCloudHostIntegrationId.Linear;\n\t}\n\tprotected override get key(): 'linear' {\n\t\treturn 'linear';\n\t}\n\toverride get name(): string {\n\t\treturn metadata.name;\n\t}\n\toverride get domain(): string {\n\t\treturn metadata.domain;\n\t}\n\tprotected override async searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\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 Linear, drained to exhaustion and reporting whether it got there.\n\t *\n\t * The SDK read is one server-ordered query over an `or` filter across the four relationships (assigned,\n\t * created, named in an issue's content, named in a comment), so each page is 100 issues the caller has not\n\t * seen and the pages are ordered as one sequence. Draining it is therefore just following the cursor.\n\t *\n\t * Overridden rather than left to the base default because that default hardcodes `truncated: false`, and a\n\t * bounded drain that stops early is exactly the case a caller must be able to see. Three things end the\n\t * drain, and only one of them is completion:\n\t *\n\t * - the provider says there is no next page — complete;\n\t * - the backstop is reached — `truncated`, because issues beyond it exist and were not read;\n\t * - the provider claims a next page but hands back a cursor that does not advance — also `truncated`, since\n\t * the read cannot continue and the remainder is unreachable rather than absent.\n\t *\n\t * Cancellation is deliberately NOT truncation: the caller asked for the read to stop, so the partial list is\n\t * what it asked for, and flagging it would surface an incompleteness warning for a user action.\n\t */\n\tprotected override async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\tif (resources != null) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst api = await this.getProvidersApi();\n\t\tconst states = toProviderIssueStates(options?.state);\n\t\tlet cursor = undefined;\n\t\t// Starts false so an immediate cancellation, which leaves the loop before the first response, reads as\n\t\t// \"no more pages known\" rather than as an unfinished drain.\n\t\tlet hasMore = false;\n\t\tlet requestCount = 0;\n\t\tlet truncated = false;\n\t\tconst issues = [];\n\t\ttry {\n\t\t\tdo {\n\t\t\t\tif (cancellation?.aborted) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst result = await api.getIssuesForCurrentUser(toTokenWithInfo(this.id, session), {\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\t\trequestCount += 1;\n\t\t\t\thasMore = result.paging?.more ?? false;\n\t\t\t\tconst nextCursor = result.paging?.cursor;\n\n\t\t\t\t// Keep this page before deciding whether to continue: the request is already paid for, so\n\t\t\t\t// dropping its rows would lose real results to save nothing.\n\t\t\t\tconst formattedIssues = result.values\n\t\t\t\t\t.map(issue => toIssueShape(issue, this))\n\t\t\t\t\t.filter((result): result is IssueShape => result != null);\n\t\t\t\tif (formattedIssues.length > 0) {\n\t\t\t\t\tissues.push(...formattedIssues);\n\t\t\t\t}\n\n\t\t\t\t// The provider claims more but hands back no advancing cursor: continuing would re-read the page\n\t\t\t\t// just returned. `ProvidersApi.getPagedResult` already normalizes this into `more: false`, so\n\t\t\t\t// this is a backstop for if that ever stops holding rather than a case seen today -- but the rest\n\t\t\t\t// of the account is unreachable either way, which is what `truncated` says.\n\t\t\t\tif (hasMore && (nextCursor == null || nextCursor === cursor)) {\n\t\t\t\t\ttruncated = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcursor = nextCursor;\n\t\t\t} while (requestCount < maxAccountWidePagesPerRequest && hasMore);\n\n\t\t\t// Ran out of budget with pages still to come.\n\t\t\tif (hasMore && requestCount >= maxAccountWidePagesPerRequest) {\n\t\t\t\ttruncated = true;\n\t\t\t}\n\t\t} catch (ex) {\n\t\t\tif (issues.length === 0) {\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\t// Kept what was already fetched, so the list is real but short of the account.\n\t\t\ttruncated = true;\n\t\t\tLogger.error(ex, 'searchProviderMyIssues');\n\t\t}\n\t\treturn { values: issues, truncated: truncated };\n\t}\n\tprotected override async getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\t{ key }: { id: string; key: string },\n\t\t_type: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst issue = await this.getRawProviderIssue(session, resource, key);\n\t\tconst autolinkableIssue: ProviderIssue | undefined = issue && {\n\t\t\t...issue,\n\t\t\turl: this.getIssueAutolinkLikeUrl(issue),\n\t\t};\n\t\treturn autolinkableIssue && toIssueShape(autolinkableIssue, this);\n\t}\n\tprotected override async getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t): Promise<Issue | undefined> {\n\t\tconst result = await this.getRawProviderIssue(session, resource, id);\n\t\treturn result && fromProviderIssue(result, this);\n\t}\n\n\tprotected override async getProviderIssueByResourceId(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresourceId: string,\n\t\tid: string,\n\t\t_resourceUrl: string | undefined,\n\t): Promise<Issue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tconst result = await api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resourceId,\n\t\t\tnumber: id,\n\t\t});\n\t\treturn result && fromProviderIssue(result, this);\n\t}\n\n\tprivate async getRawProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t): Promise<ProviderIssue | undefined> {\n\t\tconst api = await this.getProvidersApi();\n\t\tif (!isIssueResourceDescriptor(resource)) {\n\t\t\tLogger.error(undefined, 'getProviderIssue: resource is not an IssueResourceDescriptor');\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn api.getIssue(toTokenWithInfo(this.id, session), {\n\t\t\tresourceId: resource.id,\n\t\t\tnumber: id,\n\t\t});\n\t}\n\tprivate getIssueAutolinkLikeUrl(issue: ProviderIssue): string | null {\n\t\tconst url = issue.url;\n\t\tif (url == null) return null;\n\n\t\tconst lastSegment = url.split('/').pop();\n\t\tif (!lastSegment || issue.number === lastSegment) {\n\t\t\treturn url;\n\t\t}\n\t\treturn url.substring(0, url.length - lastSegment.length - 1);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -367,6 +367,8 @@ export type GetIssuesForCurrentUserInput = PagingInput & {
|
|
|
367
367
|
assigneeUsername?: string;
|
|
368
368
|
authorUsername?: string;
|
|
369
369
|
pageSize?: number;
|
|
370
|
+
/** Linear only (GitLab's REST read has its own `state`, not threaded here). Omitted reads open issues. */
|
|
371
|
+
states?: GitIssueState[];
|
|
370
372
|
/** See {@link GetIssuesOptions.sort}. */
|
|
371
373
|
sort?: IssueSorting;
|
|
372
374
|
};
|
|
@@ -440,6 +442,7 @@ export type GetLinearIssuesFn = (input: {
|
|
|
440
442
|
teams?: string[];
|
|
441
443
|
projects?: string[];
|
|
442
444
|
labels?: string[];
|
|
445
|
+
states?: GitIssueState[];
|
|
443
446
|
} & PagingInput, options?: EnterpriseOptions) => Promise<{
|
|
444
447
|
data: ProviderIssue[];
|
|
445
448
|
pageInfo?: PageInfo;
|
|
@@ -661,6 +664,16 @@ export interface ProviderMetadata {
|
|
|
661
664
|
* honor, which is what makes intersecting against it sufficient.
|
|
662
665
|
*/
|
|
663
666
|
supportedAccountWideIssueSorts?: IssueSorting[];
|
|
667
|
+
/**
|
|
668
|
+
* Whether an issue tracker's reads (the project-scoped read, which is `listIssueTrackerIssuesPage`, and its
|
|
669
|
+
* account-wide read) can be asked for a `state` other than open, and honor it server-side.
|
|
670
|
+
*
|
|
671
|
+
* A tracker read is open-only unless asked otherwise, so absent does not mean the read returns every state: it
|
|
672
|
+
* means only `'open'` is expressible, and `'closed'`/`'all'` are refused rather than served as the open list.
|
|
673
|
+
* Server-side, because each read drains to a page backstop: dropping states afterwards would let a project full
|
|
674
|
+
* of done work spend that budget before reaching the open issues.
|
|
675
|
+
*/
|
|
676
|
+
supportsIssueStates?: boolean;
|
|
664
677
|
/**
|
|
665
678
|
* What the provider's FILTERED issue search (`searchIssuesPage`, and the `countIssues` probe over the same
|
|
666
679
|
* criteria) can express server-side. A third, wider surface than either filter set above: it is not bound to
|
|
@@ -746,7 +759,12 @@ export declare function fromProviderPullRequestState(state: GitPullRequestState)
|
|
|
746
759
|
type PullRequestStateInput = IssueOrPullRequestState | PullRequestStateFilter;
|
|
747
760
|
/** Maps PR include/state filters to the SDK's `states` input. `undefined`/omitted preserves the open-only default. */
|
|
748
761
|
export declare function toProviderPullRequestStates(state: PullRequestStateInput | PullRequestStateInput[] | undefined): GitPullRequestState[] | undefined;
|
|
749
|
-
/**
|
|
762
|
+
/**
|
|
763
|
+
* Maps an issue state filter to the SDK's `states` input. `undefined`/omitted preserves the open-only default.
|
|
764
|
+
*
|
|
765
|
+
* Throws on any other value, which only an untyped caller can pass: read as omitted it would serve open issues as
|
|
766
|
+
* the state that was asked for. Called before a read's first request, so the throw refuses the whole read.
|
|
767
|
+
*/
|
|
750
768
|
export declare function toProviderIssueStates(state: IssueStateFilter | undefined): GitIssueState[] | undefined;
|
|
751
769
|
/**
|
|
752
770
|
* Resolves a normalized {@link ProviderScope} to the provider-appropriate read inputs, dispatching on the
|
|
@@ -515,7 +515,8 @@ const providersMetadata = {
|
|
|
515
515
|
"assignee",
|
|
516
516
|
"mention"
|
|
517
517
|
],
|
|
518
|
-
supportedIssueSorts: jiraIssueSorts
|
|
518
|
+
supportedIssueSorts: jiraIssueSorts,
|
|
519
|
+
supportsIssueStates: true
|
|
519
520
|
},
|
|
520
521
|
["jira-server"]: {
|
|
521
522
|
domain: "",
|
|
@@ -529,7 +530,8 @@ const providersMetadata = {
|
|
|
529
530
|
"assignee",
|
|
530
531
|
"mention"
|
|
531
532
|
],
|
|
532
|
-
supportedIssueSorts: jiraIssueSorts
|
|
533
|
+
supportedIssueSorts: jiraIssueSorts,
|
|
534
|
+
supportsIssueStates: true
|
|
533
535
|
},
|
|
534
536
|
["linear"]: {
|
|
535
537
|
domain: "linear.app",
|
|
@@ -539,7 +541,8 @@ const providersMetadata = {
|
|
|
539
541
|
iconKey: "linear",
|
|
540
542
|
scopes: [],
|
|
541
543
|
supportedIssueFilters: ["assignee"],
|
|
542
|
-
supportedIssueSorts: linearIssueSorts
|
|
544
|
+
supportedIssueSorts: linearIssueSorts,
|
|
545
|
+
supportsIssueStates: true
|
|
543
546
|
},
|
|
544
547
|
["trello"]: {
|
|
545
548
|
domain: "trello.com",
|
|
@@ -760,13 +763,19 @@ function toProviderPullRequestStates(state) {
|
|
|
760
763
|
const states = (Array.isArray(state) ? state : [state]).flatMap((s) => toProviderPullRequestStatesCore(s));
|
|
761
764
|
return states.length > 0 ? [...new Set(states)] : void 0;
|
|
762
765
|
}
|
|
763
|
-
/**
|
|
766
|
+
/**
|
|
767
|
+
* Maps an issue state filter to the SDK's `states` input. `undefined`/omitted preserves the open-only default.
|
|
768
|
+
*
|
|
769
|
+
* Throws on any other value, which only an untyped caller can pass: read as omitted it would serve open issues as
|
|
770
|
+
* the state that was asked for. Called before a read's first request, so the throw refuses the whole read.
|
|
771
|
+
*/
|
|
764
772
|
function toProviderIssueStates(state) {
|
|
765
773
|
switch (state) {
|
|
774
|
+
case void 0: return;
|
|
766
775
|
case "open": return [GitIssueState.Open];
|
|
767
776
|
case "closed": return [GitIssueState.Closed];
|
|
768
777
|
case "all": return [GitIssueState.Open, GitIssueState.Closed];
|
|
769
|
-
default:
|
|
778
|
+
default: throw new Error(`Unknown issue state; expected one of open, closed, all`);
|
|
770
779
|
}
|
|
771
780
|
}
|
|
772
781
|
/**
|