Skip to content

Commit 9a2f123

Browse files
Adds Gitlab Self Managed support to Launchpad and Start Work
1 parent b2e3ea1 commit 9a2f123

File tree

16 files changed

+326
-57
lines changed

16 files changed

+326
-57
lines changed

docs/telemetry-events.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ or
319319
```typescript
320320
{
321321
'hostingProvider.key': string,
322-
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
322+
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
323323
}
324324
```
325325

@@ -330,7 +330,7 @@ or
330330
```typescript
331331
{
332332
'hostingProvider.key': string,
333-
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
333+
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
334334
}
335335
```
336336

@@ -341,7 +341,7 @@ or
341341
```typescript
342342
{
343343
'issueProvider.key': string,
344-
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
344+
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
345345
}
346346
```
347347

@@ -352,7 +352,7 @@ or
352352
```typescript
353353
{
354354
'issueProvider.key': string,
355-
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
355+
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
356356
}
357357
```
358358

@@ -373,7 +373,7 @@ or
373373
374374
```typescript
375375
{
376-
'integration.id': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
376+
'integration.id': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
377377
}
378378
```
379379

@@ -1465,7 +1465,7 @@ void
14651465
```typescript
14661466
{
14671467
'hostingProvider.key': string,
1468-
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted',
1468+
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted',
14691469
// @deprecated: true
14701470
'remoteProviders.key': string
14711471
}
@@ -1478,7 +1478,7 @@ void
14781478
```typescript
14791479
{
14801480
'hostingProvider.key': string,
1481-
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted',
1481+
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted',
14821482
// @deprecated: true
14831483
'remoteProviders.key': string
14841484
}

src/constants.integrations.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export enum HostingIntegrationId {
88
export enum SelfHostedIntegrationId {
99
GitHubEnterprise = 'github-enterprise',
1010
CloudGitHubEnterprise = 'cloud-github-enterprise',
11+
CloudGitLabSelfHosted = 'cloud-gitlab-self-hosted',
1112
GitLabSelfHosted = 'gitlab-self-hosted',
1213
}
1314

@@ -23,6 +24,7 @@ export const supportedOrderedCloudIntegrationIds = [
2324
HostingIntegrationId.GitHub,
2425
SelfHostedIntegrationId.CloudGitHubEnterprise,
2526
HostingIntegrationId.GitLab,
27+
SelfHostedIntegrationId.CloudGitLabSelfHosted,
2628
IssueIntegrationId.Jira,
2729
];
2830

@@ -59,6 +61,12 @@ export const supportedCloudIntegrationDescriptors: IntegrationDescriptor[] = [
5961
icon: 'gl-provider-gitlab',
6062
supports: ['prs', 'issues'],
6163
},
64+
{
65+
id: SelfHostedIntegrationId.CloudGitLabSelfHosted,
66+
name: 'GitLab Self-Managed',
67+
icon: 'gl-provider-gitlab',
68+
supports: ['prs', 'issues'],
69+
},
6270
{
6371
id: IssueIntegrationId.Jira,
6472
name: 'Jira',

src/git/remotes/remoteProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type RemoteProviderId =
2121
| 'gitea'
2222
| 'github'
2323
| 'cloud-github-enterprise'
24+
| 'cloud-gitlab-self-hosted'
2425
| 'gitlab'
2526
| 'google-source';
2627

src/git/remotes/remoteProviders.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,16 @@ export function loadRemoteProviders(
104104

105105
if (configuredIntegrations?.length) {
106106
for (const ci of configuredIntegrations) {
107-
if (ci.integrationId === SelfHostedIntegrationId.CloudGitHubEnterprise && ci.domain) {
107+
if (
108+
(ci.integrationId === SelfHostedIntegrationId.CloudGitHubEnterprise ||
109+
ci.integrationId === SelfHostedIntegrationId.CloudGitLabSelfHosted) &&
110+
ci.domain
111+
) {
108112
const matcher = ci.domain.toLocaleLowerCase();
109113
const providerCreator = (_container: Container, domain: string, path: string) =>
110-
new GitHubRemote(domain, path);
114+
ci.integrationId === SelfHostedIntegrationId.CloudGitHubEnterprise
115+
? new GitHubRemote(domain, path)
116+
: new GitLabRemote(domain, path);
111117
const provider = {
112118
custom: false,
113119
matcher: matcher,

src/plus/integrations/authentication/gitlab.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Disposable, QuickInputButton } from 'vscode';
22
import { env, ThemeIcon, Uri, window } from 'vscode';
3-
import type { SelfHostedIntegrationId } from '../../../constants.integrations';
4-
import { HostingIntegrationId } from '../../../constants.integrations';
3+
import { HostingIntegrationId, SelfHostedIntegrationId } from '../../../constants.integrations';
54
import type { Container } from '../../../container';
65
import type {
76
IntegrationAuthenticationService,
@@ -95,6 +94,16 @@ export class GitLabLocalAuthenticationProvider extends LocalIntegrationAuthentic
9594
}
9695
}
9796

97+
export class GitLabSelfHostedCloudAuthenticationProvider extends CloudIntegrationAuthenticationProvider<SelfHostedIntegrationId.CloudGitLabSelfHosted> {
98+
protected override getCompletionInputTitle(): string {
99+
throw new Error('Connect to GitLab Enterprise');
100+
}
101+
102+
protected override get authProviderId(): SelfHostedIntegrationId.CloudGitLabSelfHosted {
103+
return SelfHostedIntegrationId.CloudGitLabSelfHosted;
104+
}
105+
}
106+
98107
export class GitLabCloudAuthenticationProvider extends CloudIntegrationAuthenticationProvider<GitLabId> {
99108
protected override get authProviderId(): GitLabId {
100109
return HostingIntegrationId.GitLab;

src/plus/integrations/authentication/integrationAuthentication.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,9 @@ export abstract class CloudIntegrationAuthenticationProvider<
376376
if (
377377
session?.expiresIn === 0 &&
378378
(this.authProviderId === HostingIntegrationId.GitHub ||
379-
this.authProviderId === SelfHostedIntegrationId.CloudGitHubEnterprise)
379+
this.authProviderId === SelfHostedIntegrationId.CloudGitHubEnterprise ||
380+
// Note: added GitLab self managed here because the cloud token is always a PAT, and the api does not know when it expires, nor can it refresh it
381+
this.authProviderId === SelfHostedIntegrationId.CloudGitLabSelfHosted)
380382
) {
381383
// It never expires so don't refresh it frequently:
382384
session.expiresIn = maxSmallIntegerV8; // maximum expiration length
@@ -642,6 +644,11 @@ export class IntegrationAuthenticationService implements Disposable {
642644
await import(/* webpackChunkName: "integrations" */ './gitlab')
643645
).GitLabLocalAuthenticationProvider(this.container, this, HostingIntegrationId.GitLab);
644646
break;
647+
case SelfHostedIntegrationId.CloudGitLabSelfHosted:
648+
provider = new (
649+
await import(/* webpackChunkName: "integrations" */ './gitlab')
650+
).GitLabSelfHostedCloudAuthenticationProvider(this.container, this);
651+
break;
645652
case SelfHostedIntegrationId.GitLabSelfHosted:
646653
provider = new (
647654
await import(/* webpackChunkName: "integrations" */ './gitlab')

src/plus/integrations/authentication/models.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,15 @@ export interface CloudIntegrationConnection {
4040
domain: string;
4141
}
4242

43-
export type CloudIntegrationType = 'jira' | 'trello' | 'gitlab' | 'github' | 'bitbucket' | 'azure' | 'githubEnterprise';
43+
export type CloudIntegrationType =
44+
| 'jira'
45+
| 'trello'
46+
| 'gitlab'
47+
| 'github'
48+
| 'bitbucket'
49+
| 'azure'
50+
| 'githubEnterprise'
51+
| 'gitlabSelfHosted';
4452

4553
export type CloudIntegrationAuthType = 'oauth' | 'pat';
4654

@@ -62,6 +70,7 @@ export const toIntegrationId: { [key in CloudIntegrationType]: IntegrationId } =
6270
gitlab: HostingIntegrationId.GitLab,
6371
github: HostingIntegrationId.GitHub,
6472
githubEnterprise: SelfHostedIntegrationId.CloudGitHubEnterprise,
73+
gitlabSelfHosted: SelfHostedIntegrationId.CloudGitLabSelfHosted,
6574
bitbucket: HostingIntegrationId.Bitbucket,
6675
azure: HostingIntegrationId.AzureDevOps,
6776
};
@@ -74,6 +83,7 @@ export const toCloudIntegrationType: { [key in IntegrationId]: CloudIntegrationT
7483
[HostingIntegrationId.Bitbucket]: 'bitbucket',
7584
[HostingIntegrationId.AzureDevOps]: 'azure',
7685
[SelfHostedIntegrationId.CloudGitHubEnterprise]: 'githubEnterprise',
86+
[SelfHostedIntegrationId.CloudGitLabSelfHosted]: 'gitlabSelfHosted',
7787
[SelfHostedIntegrationId.GitHubEnterprise]: undefined,
7888
[SelfHostedIntegrationId.GitLabSelfHosted]: undefined,
7989
};

src/plus/integrations/integrationService.ts

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import type {
4545
} from './integration';
4646
import { isHostingIntegrationId, isSelfHostedIntegrationId } from './providers/models';
4747
import type { ProvidersApi } from './providers/providersApi';
48-
import { isGitHubDotCom } from './providers/utils';
48+
import { isGitHubDotCom, isGitLabDotCom } from './providers/utils';
4949

5050
export interface ConnectionStateChangeEvent {
5151
key: string;
@@ -136,7 +136,11 @@ export class IntegrationService implements Disposable {
136136

137137
private async *getSupportedCloudIntegrations(domainsById: Map<IntegrationId, string>): AsyncIterable<Integration> {
138138
for (const id of getSupportedCloudIntegrationIds()) {
139-
if (id === SelfHostedIntegrationId.CloudGitHubEnterprise && !domainsById.has(id)) {
139+
if (
140+
(id === SelfHostedIntegrationId.CloudGitHubEnterprise ||
141+
id === SelfHostedIntegrationId.CloudGitLabSelfHosted) &&
142+
!domainsById.has(id)
143+
) {
140144
try {
141145
// Try getting whatever we have now because we will need to disconnect
142146
yield this.get(id);
@@ -452,7 +456,10 @@ export class IntegrationService implements Disposable {
452456
}
453457

454458
get(
455-
id: SupportedHostingIntegrationIds | SelfHostedIntegrationId.CloudGitHubEnterprise,
459+
id:
460+
| SupportedHostingIntegrationIds
461+
| SelfHostedIntegrationId.CloudGitHubEnterprise
462+
| SelfHostedIntegrationId.CloudGitLabSelfHosted,
456463
): Promise<HostingIntegration>;
457464
get(id: SupportedIssueIntegrationIds): Promise<IssueIntegration>;
458465
get(id: SupportedSelfHostedIntegrationIds, domain: string): Promise<HostingIntegration>;
@@ -527,6 +534,47 @@ export class IntegrationService implements Disposable {
527534
await import(/* webpackChunkName: "integrations" */ './providers/gitlab')
528535
).GitLabIntegration(this.container, this.authenticationService, this.getProvidersApi.bind(this));
529536
break;
537+
case SelfHostedIntegrationId.CloudGitLabSelfHosted:
538+
if (domain == null) {
539+
integration = this.findCachedById(id);
540+
if (integration != null) {
541+
// return immediately in order to not to cache it after the "switch" block:
542+
return integration;
543+
}
544+
545+
const existingConfigured = this.authenticationService.configured?.get(
546+
SelfHostedIntegrationId.CloudGitLabSelfHosted,
547+
);
548+
if (existingConfigured?.length) {
549+
const { domain: configuredDomain } = existingConfigured[0];
550+
if (configuredDomain == null) throw new Error(`Domain is required for '${id}' integration`);
551+
integration = new (
552+
await import(/* webpackChunkName: "integrations" */ './providers/gitlab')
553+
).GitLabSelfHostedIntegration(
554+
this.container,
555+
this.authenticationService,
556+
this.getProvidersApi.bind(this),
557+
configuredDomain,
558+
id,
559+
);
560+
// assign domain because it's part of caching key:
561+
domain = configuredDomain;
562+
break;
563+
}
564+
565+
throw new Error(`Domain is required for '${id}' integration`);
566+
}
567+
568+
integration = new (
569+
await import(/* webpackChunkName: "integrations" */ './providers/gitlab')
570+
).GitLabSelfHostedIntegration(
571+
this.container,
572+
this.authenticationService,
573+
this.getProvidersApi.bind(this),
574+
domain,
575+
id,
576+
);
577+
break;
530578
case SelfHostedIntegrationId.GitLabSelfHosted:
531579
if (domain == null) throw new Error(`Domain is required for '${id}' integration`);
532580
integration = new (
@@ -536,6 +584,7 @@ export class IntegrationService implements Disposable {
536584
this.authenticationService,
537585
this.getProvidersApi.bind(this),
538586
domain,
587+
id,
539588
);
540589
break;
541590
case HostingIntegrationId.Bitbucket:
@@ -630,8 +679,13 @@ export class IntegrationService implements Disposable {
630679
}
631680
return get(HostingIntegrationId.GitHub) as RT;
632681
case 'gitlab':
633-
if (remote.provider.custom && remote.provider.domain != null) {
634-
return get(SelfHostedIntegrationId.GitLabSelfHosted, remote.provider.domain) as RT;
682+
if (remote.provider.domain != null && !isGitLabDotCom(remote.provider.domain)) {
683+
return get(
684+
remote.provider.custom
685+
? SelfHostedIntegrationId.GitLabSelfHosted
686+
: SelfHostedIntegrationId.CloudGitLabSelfHosted,
687+
remote.provider.domain,
688+
) as RT;
635689
}
636690
return get(HostingIntegrationId.GitLab) as RT;
637691
default:
@@ -771,9 +825,25 @@ export class IntegrationService implements Disposable {
771825
args: { 0: integrationIds => (integrationIds?.length ? integrationIds.join(',') : '<undefined>') },
772826
})
773827
async getMyCurrentAccounts(
774-
integrationIds: (HostingIntegrationId | SelfHostedIntegrationId.CloudGitHubEnterprise)[],
775-
): Promise<Map<HostingIntegrationId | SelfHostedIntegrationId.CloudGitHubEnterprise, Account>> {
776-
const accounts = new Map<HostingIntegrationId | SelfHostedIntegrationId.CloudGitHubEnterprise, Account>();
828+
integrationIds: (
829+
| HostingIntegrationId
830+
| SelfHostedIntegrationId.CloudGitHubEnterprise
831+
| SelfHostedIntegrationId.CloudGitLabSelfHosted
832+
)[],
833+
): Promise<
834+
Map<
835+
| HostingIntegrationId
836+
| SelfHostedIntegrationId.CloudGitHubEnterprise
837+
| SelfHostedIntegrationId.CloudGitLabSelfHosted,
838+
Account
839+
>
840+
> {
841+
const accounts = new Map<
842+
| HostingIntegrationId
843+
| SelfHostedIntegrationId.CloudGitHubEnterprise
844+
| SelfHostedIntegrationId.CloudGitLabSelfHosted,
845+
Account
846+
>();
777847
await Promise.allSettled(
778848
integrationIds.map(async integrationId => {
779849
const integration = await this.get(integrationId);
@@ -792,7 +862,11 @@ export class IntegrationService implements Disposable {
792862
args: { 0: integrationIds => (integrationIds?.length ? integrationIds.join(',') : '<undefined>'), 1: false },
793863
})
794864
async getMyPullRequests(
795-
integrationIds?: (HostingIntegrationId | SelfHostedIntegrationId.CloudGitHubEnterprise)[],
865+
integrationIds?: (
866+
| HostingIntegrationId
867+
| SelfHostedIntegrationId.CloudGitHubEnterprise
868+
| SelfHostedIntegrationId.CloudGitLabSelfHosted
869+
)[],
796870
cancellation?: CancellationToken,
797871
silent?: boolean,
798872
): Promise<IntegrationResult<SearchedPullRequest[] | undefined>> {

0 commit comments

Comments
 (0)