@gitkraken/core-gitlens 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/README.md +13 -10
- package/dist/git/context.d.ts +4 -3
- package/dist/git/context.d.ts.map +1 -1
- package/dist/git/context.js.map +1 -1
- package/dist/git/providers/operations.d.ts +1 -0
- package/dist/git/providers/operations.d.ts.map +1 -1
- package/dist/git/providers/worktrees.d.ts +2 -0
- package/dist/git/providers/worktrees.d.ts.map +1 -1
- package/dist/git/utils/conflictResolution.utils.d.ts +32 -0
- package/dist/git/utils/conflictResolution.utils.d.ts.map +1 -1
- package/dist/git/utils/conflictResolution.utils.js +95 -0
- package/dist/git/utils/conflictResolution.utils.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +1 -0
- package/dist/git-cli/providers/operations.d.ts.map +1 -1
- package/dist/git-cli/providers/operations.js +22 -1
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +2 -0
- package/dist/git-cli/providers/worktrees.d.ts.map +1 -1
- package/dist/git-cli/providers/worktrees.js +3 -0
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/plus/ai/utils/ai.utils.js +5 -5
- package/dist/plus/ai/utils/ai.utils.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +6 -13
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +5 -10
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts +11 -0
- package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/cloudIntegrationService.js +90 -0
- package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts +67 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.js +295 -0
- package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts +92 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +185 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts +62 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js +122 -0
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -0
- package/dist/plus/integrations/authentication/manualTokenProvider.d.ts +15 -0
- package/dist/plus/integrations/authentication/manualTokenProvider.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/manualTokenProvider.js +49 -0
- package/dist/plus/integrations/authentication/manualTokenProvider.js.map +1 -0
- package/dist/plus/integrations/authentication/models.d.ts +87 -0
- package/dist/plus/integrations/authentication/models.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/models.js +64 -0
- package/dist/plus/integrations/authentication/models.js.map +1 -0
- package/dist/plus/integrations/constants.d.ts +47 -0
- package/dist/plus/integrations/constants.d.ts.map +1 -0
- package/dist/plus/integrations/constants.js +128 -0
- package/dist/plus/integrations/constants.js.map +1 -0
- package/dist/plus/integrations/context.d.ts +354 -0
- package/dist/plus/integrations/context.d.ts.map +1 -0
- package/dist/plus/integrations/context.js +2 -0
- package/dist/plus/integrations/context.js.map +1 -0
- package/dist/plus/integrations/errors.d.ts +14 -0
- package/dist/plus/integrations/errors.d.ts.map +1 -0
- package/dist/plus/integrations/errors.js +19 -0
- package/dist/plus/integrations/errors.js.map +1 -0
- package/dist/plus/integrations/index.d.ts +32 -0
- package/dist/plus/integrations/index.d.ts.map +1 -0
- package/dist/plus/integrations/index.js +62 -0
- package/dist/plus/integrations/index.js.map +1 -0
- package/dist/plus/integrations/integrationService.d.ts +98 -0
- package/dist/plus/integrations/integrationService.d.ts.map +1 -0
- package/dist/plus/integrations/integrationService.js +627 -0
- package/dist/plus/integrations/integrationService.js.map +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +82 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.js +522 -0
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -0
- package/dist/plus/integrations/models/integration.d.ts +127 -0
- package/dist/plus/integrations/models/integration.d.ts.map +1 -0
- package/dist/plus/integrations/models/integration.js +456 -0
- package/dist/plus/integrations/models/integration.js.map +1 -0
- package/dist/plus/integrations/models/issuesIntegration.d.ts +28 -0
- package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -0
- package/dist/plus/integrations/models/issuesIntegration.js +101 -0
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -0
- package/dist/plus/integrations/providers/apiClients.d.ts +25 -0
- package/dist/plus/integrations/providers/apiClients.d.ts.map +1 -0
- package/dist/plus/integrations/providers/apiClients.js +39 -0
- package/dist/plus/integrations/providers/apiClients.js.map +1 -0
- package/dist/plus/integrations/providers/azure/azure.d.ts +47 -0
- package/dist/plus/integrations/providers/azure/azure.d.ts.map +1 -0
- package/dist/plus/integrations/providers/azure/azure.js +444 -0
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -0
- package/dist/plus/integrations/providers/azure/models.d.ts +286 -0
- package/dist/plus/integrations/providers/azure/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/azure/models.js +203 -0
- package/dist/plus/integrations/providers/azure/models.js.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.d.ts +95 -0
- package/dist/plus/integrations/providers/azureDevOps.d.ts.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.js +432 -0
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +42 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +513 -0
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket/models.d.ts +222 -0
- package/dist/plus/integrations/providers/bitbucket/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket/models.js +158 -0
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +120 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.js +111 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +60 -0
- package/dist/plus/integrations/providers/bitbucket-server.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server.js +138 -0
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket.d.ts +50 -0
- package/dist/plus/integrations/providers/bitbucket.d.ts.map +1 -0
- package/dist/plus/integrations/providers/bitbucket.js +181 -0
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -0
- package/dist/plus/integrations/providers/github/github.d.ts +5 -0
- package/dist/plus/integrations/providers/github/github.d.ts.map +1 -0
- package/dist/plus/integrations/providers/github/github.js +47 -0
- package/dist/plus/integrations/providers/github/github.js.map +1 -0
- package/dist/plus/integrations/providers/github/github.utils.d.ts +11 -0
- package/dist/plus/integrations/providers/github/github.utils.d.ts.map +1 -0
- package/dist/plus/integrations/providers/github/github.utils.js +6 -0
- package/dist/plus/integrations/providers/github/github.utils.js.map +1 -0
- package/dist/plus/integrations/providers/github/models.d.ts +3 -0
- package/dist/plus/integrations/providers/github/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/github/models.js +2 -0
- package/dist/plus/integrations/providers/github/models.js.map +1 -0
- package/dist/plus/integrations/providers/github.d.ts +73 -0
- package/dist/plus/integrations/providers/github.d.ts.map +1 -0
- package/dist/plus/integrations/providers/github.js +160 -0
- package/dist/plus/integrations/providers/github.js.map +1 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +63 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts.map +1 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.js +828 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts +21 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts.map +1 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.js +45 -0
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.js.map +1 -0
- package/dist/plus/integrations/providers/gitlab/models.d.ts +128 -0
- package/dist/plus/integrations/providers/gitlab/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/gitlab/models.js +96 -0
- package/dist/plus/integrations/providers/gitlab/models.js.map +1 -0
- package/dist/plus/integrations/providers/gitlab.d.ts +79 -0
- package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -0
- package/dist/plus/integrations/providers/gitlab.js +253 -0
- package/dist/plus/integrations/providers/gitlab.js.map +1 -0
- package/dist/plus/integrations/providers/jira.d.ts +46 -0
- package/dist/plus/integrations/providers/jira.d.ts.map +1 -0
- package/dist/plus/integrations/providers/jira.js +255 -0
- package/dist/plus/integrations/providers/jira.js.map +1 -0
- package/dist/plus/integrations/providers/linear.d.ts +47 -0
- package/dist/plus/integrations/providers/linear.d.ts.map +1 -0
- package/dist/plus/integrations/providers/linear.js +198 -0
- package/dist/plus/integrations/providers/linear.js.map +1 -0
- package/dist/plus/integrations/providers/models.d.ts +433 -0
- package/dist/plus/integrations/providers/models.d.ts.map +1 -0
- package/dist/plus/integrations/providers/models.js +602 -0
- package/dist/plus/integrations/providers/models.js.map +1 -0
- package/dist/plus/integrations/providers/providersApi.d.ts +129 -0
- package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -0
- package/dist/plus/integrations/providers/providersApi.js +602 -0
- package/dist/plus/integrations/providers/providersApi.js.map +1 -0
- package/dist/plus/integrations/providers/utils.d.ts +54 -0
- package/dist/plus/integrations/providers/utils.d.ts.map +1 -0
- package/dist/plus/integrations/providers/utils.js +262 -0
- package/dist/plus/integrations/providers/utils.js.map +1 -0
- package/dist/plus/integrations/telemetry.d.ts +26 -0
- package/dist/plus/integrations/telemetry.d.ts.map +1 -0
- package/dist/plus/integrations/telemetry.js +11 -0
- package/dist/plus/integrations/telemetry.js.map +1 -0
- package/dist/plus/integrations/utils/integration.utils.d.ts +14 -0
- package/dist/plus/integrations/utils/integration.utils.d.ts.map +1 -0
- package/dist/plus/integrations/utils/integration.utils.js +111 -0
- package/dist/plus/integrations/utils/integration.utils.js.map +1 -0
- package/dist/utils/disposable.d.ts +15 -2
- package/dist/utils/disposable.d.ts.map +1 -1
- package/dist/utils/disposable.js.map +1 -1
- package/package.json +43 -2
- package/src/git/context.ts +4 -3
- package/src/git/providers/operations.ts +1 -1
- package/src/git/providers/worktrees.ts +14 -2
- package/src/git/utils/conflictResolution.utils.ts +126 -0
- package/src/git-cli/providers/operations.ts +22 -2
- package/src/git-cli/providers/worktrees.ts +17 -2
- package/src/plus/ai/utils/ai.utils.ts +5 -5
- package/src/plus/git-github/api/github.ts +13 -25
- package/src/plus/integrations/authentication/cloudIntegrationService.ts +120 -0
- package/src/plus/integrations/authentication/configuredIntegrationService.ts +414 -0
- package/src/plus/integrations/authentication/integrationAuthenticationProvider.ts +289 -0
- package/src/plus/integrations/authentication/integrationAuthenticationService.ts +144 -0
- package/src/plus/integrations/authentication/manualTokenProvider.ts +72 -0
- package/src/plus/integrations/authentication/models.ts +168 -0
- package/src/plus/integrations/constants.ts +169 -0
- package/src/plus/integrations/context.ts +383 -0
- package/src/plus/integrations/errors.ts +35 -0
- package/src/plus/integrations/index.ts +119 -0
- package/src/plus/integrations/integrationService.ts +924 -0
- package/src/plus/integrations/models/gitHostIntegration.ts +738 -0
- package/src/plus/integrations/models/integration.ts +657 -0
- package/src/plus/integrations/models/issuesIntegration.ts +124 -0
- package/src/plus/integrations/providers/apiClients.ts +72 -0
- package/src/plus/integrations/providers/azure/azure.ts +691 -0
- package/src/plus/integrations/providers/azure/models.ts +605 -0
- package/src/plus/integrations/providers/azureDevOps.ts +677 -0
- package/src/plus/integrations/providers/bitbucket/bitbucket.ts +774 -0
- package/src/plus/integrations/providers/bitbucket/models.ts +445 -0
- package/src/plus/integrations/providers/bitbucket-server/models.ts +252 -0
- package/src/plus/integrations/providers/bitbucket-server.ts +282 -0
- package/src/plus/integrations/providers/bitbucket.ts +350 -0
- package/src/plus/integrations/providers/github/github.ts +64 -0
- package/src/plus/integrations/providers/github/github.utils.ts +23 -0
- package/src/plus/integrations/providers/github/models.ts +29 -0
- package/src/plus/integrations/providers/github.ts +366 -0
- package/src/plus/integrations/providers/gitlab/gitlab.ts +1210 -0
- package/src/plus/integrations/providers/gitlab/gitlab.utils.ts +71 -0
- package/src/plus/integrations/providers/gitlab/models.ts +257 -0
- package/src/plus/integrations/providers/gitlab.ts +473 -0
- package/src/plus/integrations/providers/jira.ts +359 -0
- package/src/plus/integrations/providers/linear.ts +275 -0
- package/src/plus/integrations/providers/models.ts +1114 -0
- package/src/plus/integrations/providers/providersApi.ts +1126 -0
- package/src/plus/integrations/providers/utils.ts +359 -0
- package/src/plus/integrations/telemetry.ts +29 -0
- package/src/plus/integrations/utils/integration.utils.ts +138 -0
- package/src/utils/disposable.ts +15 -2
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import type { Account } from '../../../git/models/author.js';
|
|
2
|
+
import type { AutolinkReference, DynamicAutolinkReference } from '../../../git/models/autolink.js';
|
|
3
|
+
import type { Issue, IssueShape } from '../../../git/models/issue.js';
|
|
4
|
+
import type { IssueOrPullRequest } from '../../../git/models/issueOrPullRequest.js';
|
|
5
|
+
import type { IssueResourceDescriptor } from '../../../git/models/resourceDescriptor.js';
|
|
6
|
+
import { filterMap, flatten } from '../../../utils/iterable.js';
|
|
7
|
+
import { Logger } from '../../../utils/logger.js';
|
|
8
|
+
import type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';
|
|
9
|
+
import type { ProviderAuthenticationSession } from '../authentication/models.js';
|
|
10
|
+
import { toTokenWithInfo } from '../authentication/models.js';
|
|
11
|
+
import { IssuesCloudHostIntegrationId } from '../constants.js';
|
|
12
|
+
import { IssuesIntegration } from '../models/issuesIntegration.js';
|
|
13
|
+
import { IssueFilter, providersMetadata, toAccount, toIssueShape } from './models.js';
|
|
14
|
+
|
|
15
|
+
const metadata = providersMetadata[IssuesCloudHostIntegrationId.Jira];
|
|
16
|
+
const authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });
|
|
17
|
+
const maxPagesPerRequest = 10;
|
|
18
|
+
|
|
19
|
+
export type JiraBaseDescriptor = IssueResourceDescriptor;
|
|
20
|
+
|
|
21
|
+
export interface JiraOrganizationDescriptor extends JiraBaseDescriptor {
|
|
22
|
+
url: string;
|
|
23
|
+
avatarUrl: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface JiraProjectDescriptor extends JiraBaseDescriptor {
|
|
27
|
+
resourceId: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class JiraIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Jira> {
|
|
31
|
+
readonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;
|
|
32
|
+
readonly id = IssuesCloudHostIntegrationId.Jira;
|
|
33
|
+
protected readonly key = this.id;
|
|
34
|
+
readonly name: string = 'Jira';
|
|
35
|
+
|
|
36
|
+
get domain(): string {
|
|
37
|
+
return metadata.domain;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected get apiBaseUrl(): string {
|
|
41
|
+
return 'https://api.atlassian.com';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private _autolinks: Map<string, (AutolinkReference | DynamicAutolinkReference)[]> | undefined;
|
|
45
|
+
override async autolinks(): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {
|
|
46
|
+
const connected = this.maybeConnected ?? (await this.isConnected());
|
|
47
|
+
if (!connected || this._session == null || this._organizations == null || this._projects == null) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const cachedAutolinks = this._autolinks?.get(this._session.accessToken);
|
|
52
|
+
if (cachedAutolinks != null) return cachedAutolinks;
|
|
53
|
+
|
|
54
|
+
const autolinks: (AutolinkReference | DynamicAutolinkReference)[] = [];
|
|
55
|
+
const organizations = this._organizations.get(this._session.accessToken);
|
|
56
|
+
if (organizations != null) {
|
|
57
|
+
for (const organization of organizations) {
|
|
58
|
+
const projects = this._projects.get(`${this._session.accessToken}:${organization.id}`);
|
|
59
|
+
if (projects != null) {
|
|
60
|
+
for (const project of projects) {
|
|
61
|
+
const dashedPrefix = `${project.key}-`;
|
|
62
|
+
const underscoredPrefix = `${project.key}_`;
|
|
63
|
+
autolinks.push({
|
|
64
|
+
prefix: dashedPrefix,
|
|
65
|
+
url: `${organization.url}/browse/${dashedPrefix}<num>`,
|
|
66
|
+
alphanumeric: false,
|
|
67
|
+
ignoreCase: false,
|
|
68
|
+
title: `Open Issue ${dashedPrefix}<num> on ${organization.name}`,
|
|
69
|
+
|
|
70
|
+
type: 'issue',
|
|
71
|
+
description: `${organization.name} Issue ${dashedPrefix}<num>`,
|
|
72
|
+
descriptor: { ...organization },
|
|
73
|
+
});
|
|
74
|
+
autolinks.push({
|
|
75
|
+
prefix: underscoredPrefix,
|
|
76
|
+
url: `${organization.url}/browse/${dashedPrefix}<num>`,
|
|
77
|
+
alphanumeric: false,
|
|
78
|
+
ignoreCase: false,
|
|
79
|
+
referenceType: 'branch',
|
|
80
|
+
title: `Open Issue ${dashedPrefix}<num> on ${organization.name}`,
|
|
81
|
+
|
|
82
|
+
type: 'issue',
|
|
83
|
+
description: `${organization.name} Issue ${dashedPrefix}<num>`,
|
|
84
|
+
descriptor: { ...organization },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this._autolinks ??= new Map<string, (AutolinkReference | DynamicAutolinkReference)[]>();
|
|
92
|
+
this._autolinks.set(this._session.accessToken, autolinks);
|
|
93
|
+
|
|
94
|
+
return autolinks;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
protected override async getProviderAccountForResource(
|
|
98
|
+
session: ProviderAuthenticationSession,
|
|
99
|
+
resource: JiraOrganizationDescriptor,
|
|
100
|
+
): Promise<Account | undefined> {
|
|
101
|
+
const api = await this.getProvidersApi();
|
|
102
|
+
const user = await api.getCurrentUserForResource(toTokenWithInfo(this.id, session), resource.id);
|
|
103
|
+
|
|
104
|
+
if (user == null) return undefined;
|
|
105
|
+
return toAccount(user, this);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _organizations: Map<string, JiraOrganizationDescriptor[] | undefined> | undefined;
|
|
109
|
+
protected override async getProviderResourcesForUser(
|
|
110
|
+
session: ProviderAuthenticationSession,
|
|
111
|
+
force: boolean = false,
|
|
112
|
+
): Promise<JiraOrganizationDescriptor[] | undefined> {
|
|
113
|
+
const { accessToken } = session;
|
|
114
|
+
this._organizations ??= new Map<string, JiraOrganizationDescriptor[] | undefined>();
|
|
115
|
+
|
|
116
|
+
const cachedResources = this._organizations.get(accessToken);
|
|
117
|
+
|
|
118
|
+
if (cachedResources == null || force) {
|
|
119
|
+
const api = await this.getProvidersApi();
|
|
120
|
+
const resources = await api.getJiraResourcesForCurrentUser(toTokenWithInfo(this.id, session));
|
|
121
|
+
this._organizations.set(
|
|
122
|
+
accessToken,
|
|
123
|
+
resources != null ? resources.map(r => ({ ...r, key: r.id })) : undefined,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return this._organizations.get(accessToken);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private _projects: Map<string, JiraProjectDescriptor[] | undefined> | undefined;
|
|
131
|
+
protected override async getProviderProjectsForResources(
|
|
132
|
+
session: ProviderAuthenticationSession,
|
|
133
|
+
resources: JiraOrganizationDescriptor[],
|
|
134
|
+
force: boolean = false,
|
|
135
|
+
): Promise<JiraProjectDescriptor[] | undefined> {
|
|
136
|
+
const { accessToken } = session;
|
|
137
|
+
this._projects ??= new Map<string, JiraProjectDescriptor[] | undefined>();
|
|
138
|
+
|
|
139
|
+
let resourcesWithoutProjects = [];
|
|
140
|
+
if (force) {
|
|
141
|
+
resourcesWithoutProjects = resources;
|
|
142
|
+
} else {
|
|
143
|
+
for (const resource of resources) {
|
|
144
|
+
const resourceKey = `${accessToken}:${resource.id}`;
|
|
145
|
+
const cachedProjects = this._projects.get(resourceKey);
|
|
146
|
+
if (cachedProjects == null) {
|
|
147
|
+
resourcesWithoutProjects.push(resource);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (resourcesWithoutProjects.length > 0) {
|
|
153
|
+
const api = await this.getProvidersApi();
|
|
154
|
+
const jiraProjectBaseDescriptors = await api.getJiraProjectsForResources(
|
|
155
|
+
toTokenWithInfo(this.id, session),
|
|
156
|
+
resourcesWithoutProjects.map(r => r.id),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
for (const resource of resourcesWithoutProjects) {
|
|
160
|
+
const projects = jiraProjectBaseDescriptors?.filter(p => p.resourceId === resource.id);
|
|
161
|
+
if (projects != null) {
|
|
162
|
+
this._projects.set(
|
|
163
|
+
`${accessToken}:${resource.id}`,
|
|
164
|
+
projects.map(p => ({ ...p })),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return resources.reduce<JiraProjectDescriptor[]>((projects, resource) => {
|
|
171
|
+
const resourceProjects = this._projects!.get(`${accessToken}:${resource.id}`);
|
|
172
|
+
if (resourceProjects != null) {
|
|
173
|
+
projects.push(...resourceProjects);
|
|
174
|
+
}
|
|
175
|
+
return projects;
|
|
176
|
+
}, []);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
protected override async getProviderIssuesForProject(
|
|
180
|
+
session: ProviderAuthenticationSession,
|
|
181
|
+
project: JiraProjectDescriptor,
|
|
182
|
+
options?: { user: string; filters: IssueFilter[] },
|
|
183
|
+
): Promise<IssueShape[] | undefined> {
|
|
184
|
+
let results;
|
|
185
|
+
const tokenWithInfo = toTokenWithInfo(this.id, session);
|
|
186
|
+
|
|
187
|
+
const api = await this.getProvidersApi();
|
|
188
|
+
|
|
189
|
+
const getSearchedUserIssuesForFilter = async (
|
|
190
|
+
user: string,
|
|
191
|
+
filter: IssueFilter,
|
|
192
|
+
): Promise<IssueShape[] | undefined> => {
|
|
193
|
+
const results = await api.getIssuesForProject(tokenWithInfo, project.name, project.resourceId, {
|
|
194
|
+
authorLogin: filter === IssueFilter.Author ? user : undefined,
|
|
195
|
+
assigneeLogins: filter === IssueFilter.Assignee ? [user] : undefined,
|
|
196
|
+
mentionLogin: filter === IssueFilter.Mention ? user : undefined,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return results
|
|
200
|
+
?.map(issue => toIssueShape(issue, this))
|
|
201
|
+
.filter((result): result is IssueShape => result !== undefined);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
if (options?.user != null && options.filters.length > 0) {
|
|
205
|
+
const resultsPromise = Promise.allSettled(
|
|
206
|
+
options.filters.map(filter => getSearchedUserIssuesForFilter(options.user, filter)),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
results = [
|
|
210
|
+
...flatten(
|
|
211
|
+
filterMap(await resultsPromise, r =>
|
|
212
|
+
r.status === 'fulfilled' && r.value != null ? r.value : undefined,
|
|
213
|
+
),
|
|
214
|
+
),
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
const resultsById = new Map<string, IssueShape>();
|
|
218
|
+
for (const resultIssue of results) {
|
|
219
|
+
if (!resultsById.has(resultIssue.id)) {
|
|
220
|
+
resultsById.set(resultIssue.id, resultIssue);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return [...resultsById.values()];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
results = await api.getIssuesForProject(tokenWithInfo, project.name, project.resourceId);
|
|
228
|
+
return results
|
|
229
|
+
?.map(issue => toIssueShape(issue, this))
|
|
230
|
+
.filter((result): result is IssueShape => result !== undefined);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
protected override async searchProviderMyIssues(
|
|
234
|
+
session: ProviderAuthenticationSession,
|
|
235
|
+
resources?: JiraOrganizationDescriptor[],
|
|
236
|
+
cancellation?: AbortSignal,
|
|
237
|
+
): Promise<IssueShape[] | undefined> {
|
|
238
|
+
const myResources = resources ?? (await this.getProviderResourcesForUser(session));
|
|
239
|
+
if (!myResources) return undefined;
|
|
240
|
+
|
|
241
|
+
const api = await this.getProvidersApi();
|
|
242
|
+
|
|
243
|
+
const results: IssueShape[] = [];
|
|
244
|
+
for (const resource of myResources) {
|
|
245
|
+
if (cancellation?.aborted) break;
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
let cursor = undefined;
|
|
249
|
+
let hasMore = false;
|
|
250
|
+
let requestCount = 0;
|
|
251
|
+
do {
|
|
252
|
+
if (cancellation?.aborted) break;
|
|
253
|
+
|
|
254
|
+
const resourceIssues = await api.getIssuesForResourceForCurrentUser(
|
|
255
|
+
toTokenWithInfo(this.id, session),
|
|
256
|
+
resource.id,
|
|
257
|
+
{
|
|
258
|
+
cursor: cursor,
|
|
259
|
+
},
|
|
260
|
+
);
|
|
261
|
+
requestCount += 1;
|
|
262
|
+
hasMore = resourceIssues.paging?.more ?? false;
|
|
263
|
+
cursor = resourceIssues.paging?.cursor;
|
|
264
|
+
const formattedIssues = resourceIssues.values
|
|
265
|
+
.map(issue => toIssueShape(issue, this))
|
|
266
|
+
.filter((result): result is IssueShape => result != null);
|
|
267
|
+
if (formattedIssues.length > 0) {
|
|
268
|
+
results.push(...formattedIssues);
|
|
269
|
+
}
|
|
270
|
+
} while (requestCount < maxPagesPerRequest && hasMore);
|
|
271
|
+
} catch (ex) {
|
|
272
|
+
// TODO: We need a better way to message the failure to the user here.
|
|
273
|
+
// This is a stopgap to prevent one bag org from throwing and preventing any issues from being returned.
|
|
274
|
+
Logger.error(ex, 'searchProviderMyIssues');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return results;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
protected override async getProviderLinkedIssueOrPullRequest(
|
|
282
|
+
session: ProviderAuthenticationSession,
|
|
283
|
+
resource: JiraOrganizationDescriptor,
|
|
284
|
+
{ key }: { id: string; key: string },
|
|
285
|
+
): Promise<IssueOrPullRequest | undefined> {
|
|
286
|
+
const api = await this.getProvidersApi();
|
|
287
|
+
const issue = await api.getIssue(toTokenWithInfo(this.id, session), {
|
|
288
|
+
resourceId: resource.id,
|
|
289
|
+
number: key,
|
|
290
|
+
});
|
|
291
|
+
return issue != null ? toIssueShape(issue, this) : undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
protected override async getProviderIssue(
|
|
295
|
+
session: ProviderAuthenticationSession,
|
|
296
|
+
resource: JiraOrganizationDescriptor,
|
|
297
|
+
id: string,
|
|
298
|
+
): Promise<Issue | undefined> {
|
|
299
|
+
const api = await this.getProvidersApi();
|
|
300
|
+
const apiResult = await api.getIssue(toTokenWithInfo(this.id, session), {
|
|
301
|
+
resourceId: resource.id,
|
|
302
|
+
number: id,
|
|
303
|
+
});
|
|
304
|
+
const issue = apiResult != null ? toIssueShape(apiResult, this) : undefined;
|
|
305
|
+
return issue != null ? { ...issue, type: 'issue' } : undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
protected override async providerOnConnect(): Promise<void> {
|
|
309
|
+
this._autolinks = undefined;
|
|
310
|
+
if (this._session == null) return;
|
|
311
|
+
|
|
312
|
+
const storedOrganizations = this.ctx.storage.get(`jira:${this._session.accessToken}:organizations`);
|
|
313
|
+
const storedProjects = this.ctx.storage.get(`jira:${this._session.accessToken}:projects`);
|
|
314
|
+
|
|
315
|
+
let organizations = storedOrganizations?.data?.map((o: JiraOrganizationDescriptor) => ({ ...o }));
|
|
316
|
+
|
|
317
|
+
let projects = storedProjects?.data?.map((p: JiraProjectDescriptor) => ({ ...p }));
|
|
318
|
+
|
|
319
|
+
if (storedOrganizations == null) {
|
|
320
|
+
organizations = await this.getProviderResourcesForUser(this._session, true);
|
|
321
|
+
// Clear all other stored organizations and projects when our session changes
|
|
322
|
+
await this.ctx.storage.deleteWithPrefix('jira');
|
|
323
|
+
await this.ctx.storage.store(`jira:${this._session.accessToken}:organizations`, {
|
|
324
|
+
v: 1,
|
|
325
|
+
timestamp: Date.now(),
|
|
326
|
+
data: organizations,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
this._organizations ??= new Map<string, JiraOrganizationDescriptor[] | undefined>();
|
|
331
|
+
this._organizations.set(this._session.accessToken, organizations);
|
|
332
|
+
|
|
333
|
+
if (storedProjects == null && organizations?.length) {
|
|
334
|
+
projects = await this.getProviderProjectsForResources(this._session, organizations);
|
|
335
|
+
await this.ctx.storage.store(`jira:${this._session.accessToken}:projects`, {
|
|
336
|
+
v: 1,
|
|
337
|
+
timestamp: Date.now(),
|
|
338
|
+
data: projects,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
this._projects ??= new Map<string, JiraProjectDescriptor[] | undefined>();
|
|
343
|
+
for (const project of projects ?? []) {
|
|
344
|
+
const projectKey = `${this._session.accessToken}:${project.resourceId}`;
|
|
345
|
+
const projects = this._projects.get(projectKey);
|
|
346
|
+
if (projects == null) {
|
|
347
|
+
this._projects.set(projectKey, [project]);
|
|
348
|
+
} else if (!projects.some(p => p.id === project.id)) {
|
|
349
|
+
projects.push(project);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
protected override providerOnDisconnect(): void {
|
|
355
|
+
this._organizations = undefined;
|
|
356
|
+
this._projects = undefined;
|
|
357
|
+
this._autolinks = undefined;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import type { Account } from '../../../git/models/author.js';
|
|
2
|
+
import type { AutolinkReference, DynamicAutolinkReference } from '../../../git/models/autolink.js';
|
|
3
|
+
import type { Issue, IssueShape } from '../../../git/models/issue.js';
|
|
4
|
+
import type { IssueOrPullRequest, IssueOrPullRequestType } from '../../../git/models/issueOrPullRequest.js';
|
|
5
|
+
import type { IssueResourceDescriptor, ResourceDescriptor } from '../../../git/models/resourceDescriptor.js';
|
|
6
|
+
import { isIssueResourceDescriptor } from '../../../git/utils/resourceDescriptor.utils.js';
|
|
7
|
+
import { Logger } from '../../../utils/logger.js';
|
|
8
|
+
import type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';
|
|
9
|
+
import type { ProviderAuthenticationSession } from '../authentication/models.js';
|
|
10
|
+
import { toTokenWithInfo } from '../authentication/models.js';
|
|
11
|
+
import { IssuesCloudHostIntegrationId } from '../constants.js';
|
|
12
|
+
import { IssuesIntegration } from '../models/issuesIntegration.js';
|
|
13
|
+
import type { IssueFilter, ProviderIssue } from './models.js';
|
|
14
|
+
import { fromProviderIssue, providersMetadata, toIssueShape } from './models.js';
|
|
15
|
+
|
|
16
|
+
const metadata = providersMetadata[IssuesCloudHostIntegrationId.Linear];
|
|
17
|
+
const authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes });
|
|
18
|
+
const maxPagesPerRequest = 10;
|
|
19
|
+
|
|
20
|
+
export interface LinearTeamDescriptor extends IssueResourceDescriptor {
|
|
21
|
+
avatarUrl: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface LinearOrganizationDescriptor extends IssueResourceDescriptor {
|
|
25
|
+
url: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LinearProjectDescriptor extends IssueResourceDescriptor {}
|
|
29
|
+
|
|
30
|
+
export class LinearIntegration extends IssuesIntegration<IssuesCloudHostIntegrationId.Linear> {
|
|
31
|
+
private _autolinks: Map<string, (AutolinkReference | DynamicAutolinkReference)[]> | undefined;
|
|
32
|
+
override async autolinks(): Promise<(AutolinkReference | DynamicAutolinkReference)[]> {
|
|
33
|
+
const connected = this.maybeConnected ?? (await this.isConnected());
|
|
34
|
+
if (!connected || this._session == null) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const cachedAutolinks = this._autolinks?.get(this._session.accessToken);
|
|
39
|
+
if (cachedAutolinks != null) return cachedAutolinks;
|
|
40
|
+
|
|
41
|
+
const organization = await this.getOrganization(this._session);
|
|
42
|
+
if (organization == null) return [];
|
|
43
|
+
|
|
44
|
+
const autolinks: (AutolinkReference | DynamicAutolinkReference)[] = [];
|
|
45
|
+
|
|
46
|
+
const teams = await this.getTeams(this._session);
|
|
47
|
+
for (const team of teams ?? []) {
|
|
48
|
+
const dashedPrefix = `${team.key}-`;
|
|
49
|
+
const underscoredPrefix = `${team.key}_`;
|
|
50
|
+
|
|
51
|
+
autolinks.push({
|
|
52
|
+
prefix: dashedPrefix,
|
|
53
|
+
url: `${organization.url}/issue/${dashedPrefix}<num>`,
|
|
54
|
+
alphanumeric: false,
|
|
55
|
+
ignoreCase: false,
|
|
56
|
+
title: `Open Issue ${dashedPrefix}<num> on ${organization.name}`,
|
|
57
|
+
|
|
58
|
+
type: 'issue',
|
|
59
|
+
description: `${organization.name} Issue ${dashedPrefix}<num>`,
|
|
60
|
+
descriptor: { ...organization },
|
|
61
|
+
});
|
|
62
|
+
autolinks.push({
|
|
63
|
+
prefix: underscoredPrefix,
|
|
64
|
+
url: `${organization.url}/issue/${dashedPrefix}<num>`,
|
|
65
|
+
alphanumeric: false,
|
|
66
|
+
ignoreCase: false,
|
|
67
|
+
referenceType: 'branch',
|
|
68
|
+
title: `Open Issue ${dashedPrefix}<num> on ${organization.name}`,
|
|
69
|
+
|
|
70
|
+
type: 'issue',
|
|
71
|
+
description: `${organization.name} Issue ${dashedPrefix}<num>`,
|
|
72
|
+
descriptor: { ...organization },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this._autolinks ??= new Map<string, (AutolinkReference | DynamicAutolinkReference)[]>();
|
|
77
|
+
this._autolinks.set(this._session.accessToken, autolinks);
|
|
78
|
+
|
|
79
|
+
return autolinks;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private _organizations: Map<string, LinearOrganizationDescriptor | undefined> | undefined;
|
|
83
|
+
private async getOrganization(
|
|
84
|
+
session: ProviderAuthenticationSession,
|
|
85
|
+
force: boolean = false,
|
|
86
|
+
): Promise<LinearOrganizationDescriptor | undefined> {
|
|
87
|
+
const { accessToken } = session;
|
|
88
|
+
this._organizations ??= new Map<string, LinearOrganizationDescriptor | undefined>();
|
|
89
|
+
|
|
90
|
+
const cachedResources = this._organizations.get(accessToken);
|
|
91
|
+
|
|
92
|
+
if (cachedResources == null || force) {
|
|
93
|
+
const api = await this.getProvidersApi();
|
|
94
|
+
const organization = await api.getLinearOrganization(toTokenWithInfo(this.id, session));
|
|
95
|
+
const descriptor: LinearOrganizationDescriptor | undefined = organization && {
|
|
96
|
+
id: organization.id,
|
|
97
|
+
key: organization.key,
|
|
98
|
+
name: organization.name,
|
|
99
|
+
url: organization.url,
|
|
100
|
+
};
|
|
101
|
+
if (descriptor) {
|
|
102
|
+
this._organizations.set(accessToken, descriptor);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return this._organizations.get(accessToken);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private _teams: Map<string, LinearTeamDescriptor[] | undefined> | undefined;
|
|
110
|
+
private async getTeams(
|
|
111
|
+
session: ProviderAuthenticationSession,
|
|
112
|
+
force: boolean = false,
|
|
113
|
+
): Promise<LinearTeamDescriptor[] | undefined> {
|
|
114
|
+
const { accessToken } = session;
|
|
115
|
+
this._teams ??= new Map<string, LinearTeamDescriptor[] | undefined>();
|
|
116
|
+
|
|
117
|
+
const cachedResources = this._teams.get(accessToken);
|
|
118
|
+
|
|
119
|
+
if (cachedResources == null || force) {
|
|
120
|
+
const api = await this.getProvidersApi();
|
|
121
|
+
const teams = await api.getLinearTeamsForCurrentUser(toTokenWithInfo(this.id, session));
|
|
122
|
+
const descriptors: LinearTeamDescriptor[] | undefined = teams?.map(t => ({
|
|
123
|
+
id: t.id,
|
|
124
|
+
key: t.key,
|
|
125
|
+
name: t.name,
|
|
126
|
+
avatarUrl: t.iconUrl,
|
|
127
|
+
}));
|
|
128
|
+
if (descriptors) {
|
|
129
|
+
this._teams.set(accessToken, descriptors);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return this._teams.get(accessToken);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected override getProviderResourcesForUser(
|
|
137
|
+
_session: ProviderAuthenticationSession,
|
|
138
|
+
): Promise<ResourceDescriptor[] | undefined> {
|
|
139
|
+
throw new Error('Method not implemented.');
|
|
140
|
+
}
|
|
141
|
+
protected override getProviderProjectsForResources(
|
|
142
|
+
_session: ProviderAuthenticationSession,
|
|
143
|
+
_resources: ResourceDescriptor[],
|
|
144
|
+
): Promise<ResourceDescriptor[] | undefined> {
|
|
145
|
+
throw new Error('Method not implemented.');
|
|
146
|
+
}
|
|
147
|
+
readonly authProvider: IntegrationAuthenticationProviderDescriptor = authProvider;
|
|
148
|
+
|
|
149
|
+
protected override getProviderAccountForResource(
|
|
150
|
+
_session: ProviderAuthenticationSession,
|
|
151
|
+
_resource: ResourceDescriptor,
|
|
152
|
+
): Promise<Account | undefined> {
|
|
153
|
+
throw new Error('Method not implemented.');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
protected override getProviderIssuesForProject(
|
|
157
|
+
_session: ProviderAuthenticationSession,
|
|
158
|
+
_project: ResourceDescriptor,
|
|
159
|
+
_options?: { user?: string; filters?: IssueFilter[] },
|
|
160
|
+
): Promise<IssueShape[] | undefined> {
|
|
161
|
+
throw new Error('Method not implemented.');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
override get id(): IssuesCloudHostIntegrationId.Linear {
|
|
165
|
+
return IssuesCloudHostIntegrationId.Linear;
|
|
166
|
+
}
|
|
167
|
+
protected override get key(): 'linear' {
|
|
168
|
+
return 'linear';
|
|
169
|
+
}
|
|
170
|
+
override get name(): string {
|
|
171
|
+
return metadata.name;
|
|
172
|
+
}
|
|
173
|
+
override get domain(): string {
|
|
174
|
+
return metadata.domain;
|
|
175
|
+
}
|
|
176
|
+
protected override async searchProviderMyIssues(
|
|
177
|
+
session: ProviderAuthenticationSession,
|
|
178
|
+
resources?: ResourceDescriptor[],
|
|
179
|
+
cancellation?: AbortSignal,
|
|
180
|
+
): Promise<IssueShape[] | undefined> {
|
|
181
|
+
if (resources != null) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const api = await this.getProvidersApi();
|
|
186
|
+
let cursor = undefined;
|
|
187
|
+
let hasMore: boolean;
|
|
188
|
+
let requestCount = 0;
|
|
189
|
+
const issues = [];
|
|
190
|
+
try {
|
|
191
|
+
do {
|
|
192
|
+
if (cancellation?.aborted) {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const result = await api.getIssuesForCurrentUser(toTokenWithInfo(this.id, session), {
|
|
197
|
+
cursor: cursor,
|
|
198
|
+
});
|
|
199
|
+
requestCount += 1;
|
|
200
|
+
hasMore = result.paging?.more ?? false;
|
|
201
|
+
cursor = result.paging?.cursor;
|
|
202
|
+
const formattedIssues = result.values
|
|
203
|
+
.map(issue => toIssueShape(issue, this))
|
|
204
|
+
.filter((result): result is IssueShape => result != null);
|
|
205
|
+
if (formattedIssues.length > 0) {
|
|
206
|
+
issues.push(...formattedIssues);
|
|
207
|
+
}
|
|
208
|
+
} while (requestCount < maxPagesPerRequest && hasMore);
|
|
209
|
+
} catch (ex) {
|
|
210
|
+
if (issues.length === 0) {
|
|
211
|
+
throw ex;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Logger.error(ex, 'searchProviderMyIssues');
|
|
215
|
+
}
|
|
216
|
+
return issues;
|
|
217
|
+
}
|
|
218
|
+
protected override async getProviderLinkedIssueOrPullRequest(
|
|
219
|
+
session: ProviderAuthenticationSession,
|
|
220
|
+
resource: ResourceDescriptor,
|
|
221
|
+
{ key }: { id: string; key: string },
|
|
222
|
+
_type: undefined | IssueOrPullRequestType,
|
|
223
|
+
): Promise<IssueOrPullRequest | undefined> {
|
|
224
|
+
const issue = await this.getRawProviderIssue(session, resource, key);
|
|
225
|
+
const autolinkableIssue: ProviderIssue | undefined = issue && {
|
|
226
|
+
...issue,
|
|
227
|
+
url: this.getIssueAutolinkLikeUrl(issue),
|
|
228
|
+
};
|
|
229
|
+
return autolinkableIssue && toIssueShape(autolinkableIssue, this);
|
|
230
|
+
}
|
|
231
|
+
protected override async getProviderIssue(
|
|
232
|
+
session: ProviderAuthenticationSession,
|
|
233
|
+
resource: ResourceDescriptor,
|
|
234
|
+
id: string,
|
|
235
|
+
): Promise<Issue | undefined> {
|
|
236
|
+
const result = await this.getRawProviderIssue(session, resource, id);
|
|
237
|
+
return result && fromProviderIssue(result, this);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private async getRawProviderIssue(
|
|
241
|
+
session: ProviderAuthenticationSession,
|
|
242
|
+
resource: ResourceDescriptor,
|
|
243
|
+
id: string,
|
|
244
|
+
): Promise<ProviderIssue | undefined> {
|
|
245
|
+
const api = await this.getProvidersApi();
|
|
246
|
+
try {
|
|
247
|
+
if (!isIssueResourceDescriptor(resource)) {
|
|
248
|
+
Logger.error(undefined, 'getProviderIssue: resource is not an IssueResourceDescriptor');
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const result = await api.getIssue(toTokenWithInfo(this.id, session), {
|
|
253
|
+
resourceId: resource.id,
|
|
254
|
+
number: id,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (result == null) return undefined;
|
|
258
|
+
|
|
259
|
+
return result;
|
|
260
|
+
} catch (ex) {
|
|
261
|
+
Logger.error(ex, 'getProviderIssue');
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
private getIssueAutolinkLikeUrl(issue: ProviderIssue): string | null {
|
|
266
|
+
const url = issue.url;
|
|
267
|
+
if (url == null) return null;
|
|
268
|
+
|
|
269
|
+
const lastSegment = url.split('/').pop();
|
|
270
|
+
if (!lastSegment || issue.number === lastSegment) {
|
|
271
|
+
return url;
|
|
272
|
+
}
|
|
273
|
+
return url.substring(0, url.length - lastSegment.length - 1);
|
|
274
|
+
}
|
|
275
|
+
}
|