Skip to content

Commit

Permalink
Adds Gitlab Self Managed support to Launchpad and Start Work
Browse files Browse the repository at this point in the history
  • Loading branch information
axosoft-ramint committed Jan 16, 2025
1 parent b2e3ea1 commit 9a2f123
Show file tree
Hide file tree
Showing 16 changed files with 326 additions and 57 deletions.
14 changes: 7 additions & 7 deletions docs/telemetry-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ or
```typescript
{
'hostingProvider.key': string,
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand All @@ -330,7 +330,7 @@ or
```typescript
{
'hostingProvider.key': string,
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand All @@ -341,7 +341,7 @@ or
```typescript
{
'issueProvider.key': string,
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand All @@ -352,7 +352,7 @@ or
```typescript
{
'issueProvider.key': string,
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
'issueProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand All @@ -373,7 +373,7 @@ or
```typescript
{
'integration.id': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted'
'integration.id': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted'
}
```

Expand Down Expand Up @@ -1465,7 +1465,7 @@ void
```typescript
{
'hostingProvider.key': string,
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted',
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted',
// @deprecated: true
'remoteProviders.key': string
}
Expand All @@ -1478,7 +1478,7 @@ void
```typescript
{
'hostingProvider.key': string,
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'gitlab-self-hosted',
'hostingProvider.provider': 'github' | 'gitlab' | 'bitbucket' | 'azureDevOps' | 'jira' | 'trello' | 'github-enterprise' | 'cloud-github-enterprise' | 'cloud-gitlab-self-hosted' | 'gitlab-self-hosted',
// @deprecated: true
'remoteProviders.key': string
}
Expand Down
8 changes: 8 additions & 0 deletions src/constants.integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export enum HostingIntegrationId {
export enum SelfHostedIntegrationId {
GitHubEnterprise = 'github-enterprise',
CloudGitHubEnterprise = 'cloud-github-enterprise',
CloudGitLabSelfHosted = 'cloud-gitlab-self-hosted',
GitLabSelfHosted = 'gitlab-self-hosted',
}

Expand All @@ -23,6 +24,7 @@ export const supportedOrderedCloudIntegrationIds = [
HostingIntegrationId.GitHub,
SelfHostedIntegrationId.CloudGitHubEnterprise,
HostingIntegrationId.GitLab,
SelfHostedIntegrationId.CloudGitLabSelfHosted,
IssueIntegrationId.Jira,
];

Expand Down Expand Up @@ -59,6 +61,12 @@ export const supportedCloudIntegrationDescriptors: IntegrationDescriptor[] = [
icon: 'gl-provider-gitlab',
supports: ['prs', 'issues'],
},
{
id: SelfHostedIntegrationId.CloudGitLabSelfHosted,
name: 'GitLab Self-Managed',
icon: 'gl-provider-gitlab',
supports: ['prs', 'issues'],
},
{
id: IssueIntegrationId.Jira,
name: 'Jira',
Expand Down
1 change: 1 addition & 0 deletions src/git/remotes/remoteProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type RemoteProviderId =
| 'gitea'
| 'github'
| 'cloud-github-enterprise'
| 'cloud-gitlab-self-hosted'
| 'gitlab'
| 'google-source';

Expand Down
10 changes: 8 additions & 2 deletions src/git/remotes/remoteProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,16 @@ export function loadRemoteProviders(

if (configuredIntegrations?.length) {
for (const ci of configuredIntegrations) {
if (ci.integrationId === SelfHostedIntegrationId.CloudGitHubEnterprise && ci.domain) {
if (
(ci.integrationId === SelfHostedIntegrationId.CloudGitHubEnterprise ||
ci.integrationId === SelfHostedIntegrationId.CloudGitLabSelfHosted) &&
ci.domain
) {
const matcher = ci.domain.toLocaleLowerCase();
const providerCreator = (_container: Container, domain: string, path: string) =>
new GitHubRemote(domain, path);
ci.integrationId === SelfHostedIntegrationId.CloudGitHubEnterprise
? new GitHubRemote(domain, path)
: new GitLabRemote(domain, path);
const provider = {
custom: false,
matcher: matcher,
Expand Down
13 changes: 11 additions & 2 deletions src/plus/integrations/authentication/gitlab.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Disposable, QuickInputButton } from 'vscode';
import { env, ThemeIcon, Uri, window } from 'vscode';
import type { SelfHostedIntegrationId } from '../../../constants.integrations';
import { HostingIntegrationId } from '../../../constants.integrations';
import { HostingIntegrationId, SelfHostedIntegrationId } from '../../../constants.integrations';
import type { Container } from '../../../container';
import type {
IntegrationAuthenticationService,
Expand Down Expand Up @@ -95,6 +94,16 @@ export class GitLabLocalAuthenticationProvider extends LocalIntegrationAuthentic
}
}

export class GitLabSelfHostedCloudAuthenticationProvider extends CloudIntegrationAuthenticationProvider<SelfHostedIntegrationId.CloudGitLabSelfHosted> {
protected override getCompletionInputTitle(): string {
throw new Error('Connect to GitLab Enterprise');
}

protected override get authProviderId(): SelfHostedIntegrationId.CloudGitLabSelfHosted {
return SelfHostedIntegrationId.CloudGitLabSelfHosted;
}
}

export class GitLabCloudAuthenticationProvider extends CloudIntegrationAuthenticationProvider<GitLabId> {
protected override get authProviderId(): GitLabId {
return HostingIntegrationId.GitLab;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,9 @@ export abstract class CloudIntegrationAuthenticationProvider<
if (
session?.expiresIn === 0 &&
(this.authProviderId === HostingIntegrationId.GitHub ||
this.authProviderId === SelfHostedIntegrationId.CloudGitHubEnterprise)
this.authProviderId === SelfHostedIntegrationId.CloudGitHubEnterprise ||
// 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
this.authProviderId === SelfHostedIntegrationId.CloudGitLabSelfHosted)
) {
// It never expires so don't refresh it frequently:
session.expiresIn = maxSmallIntegerV8; // maximum expiration length
Expand Down Expand Up @@ -642,6 +644,11 @@ export class IntegrationAuthenticationService implements Disposable {
await import(/* webpackChunkName: "integrations" */ './gitlab')
).GitLabLocalAuthenticationProvider(this.container, this, HostingIntegrationId.GitLab);
break;
case SelfHostedIntegrationId.CloudGitLabSelfHosted:
provider = new (
await import(/* webpackChunkName: "integrations" */ './gitlab')
).GitLabSelfHostedCloudAuthenticationProvider(this.container, this);
break;
case SelfHostedIntegrationId.GitLabSelfHosted:
provider = new (
await import(/* webpackChunkName: "integrations" */ './gitlab')
Expand Down
12 changes: 11 additions & 1 deletion src/plus/integrations/authentication/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ export interface CloudIntegrationConnection {
domain: string;
}

export type CloudIntegrationType = 'jira' | 'trello' | 'gitlab' | 'github' | 'bitbucket' | 'azure' | 'githubEnterprise';
export type CloudIntegrationType =
| 'jira'
| 'trello'
| 'gitlab'
| 'github'
| 'bitbucket'
| 'azure'
| 'githubEnterprise'
| 'gitlabSelfHosted';

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

Expand All @@ -62,6 +70,7 @@ export const toIntegrationId: { [key in CloudIntegrationType]: IntegrationId } =
gitlab: HostingIntegrationId.GitLab,
github: HostingIntegrationId.GitHub,
githubEnterprise: SelfHostedIntegrationId.CloudGitHubEnterprise,
gitlabSelfHosted: SelfHostedIntegrationId.CloudGitLabSelfHosted,
bitbucket: HostingIntegrationId.Bitbucket,
azure: HostingIntegrationId.AzureDevOps,
};
Expand All @@ -74,6 +83,7 @@ export const toCloudIntegrationType: { [key in IntegrationId]: CloudIntegrationT
[HostingIntegrationId.Bitbucket]: 'bitbucket',
[HostingIntegrationId.AzureDevOps]: 'azure',
[SelfHostedIntegrationId.CloudGitHubEnterprise]: 'githubEnterprise',
[SelfHostedIntegrationId.CloudGitLabSelfHosted]: 'gitlabSelfHosted',
[SelfHostedIntegrationId.GitHubEnterprise]: undefined,
[SelfHostedIntegrationId.GitLabSelfHosted]: undefined,
};
92 changes: 83 additions & 9 deletions src/plus/integrations/integrationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import type {
} from './integration';
import { isHostingIntegrationId, isSelfHostedIntegrationId } from './providers/models';
import type { ProvidersApi } from './providers/providersApi';
import { isGitHubDotCom } from './providers/utils';
import { isGitHubDotCom, isGitLabDotCom } from './providers/utils';

export interface ConnectionStateChangeEvent {
key: string;
Expand Down Expand Up @@ -136,7 +136,11 @@ export class IntegrationService implements Disposable {

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

get(
id: SupportedHostingIntegrationIds | SelfHostedIntegrationId.CloudGitHubEnterprise,
id:
| SupportedHostingIntegrationIds
| SelfHostedIntegrationId.CloudGitHubEnterprise
| SelfHostedIntegrationId.CloudGitLabSelfHosted,
): Promise<HostingIntegration>;
get(id: SupportedIssueIntegrationIds): Promise<IssueIntegration>;
get(id: SupportedSelfHostedIntegrationIds, domain: string): Promise<HostingIntegration>;
Expand Down Expand Up @@ -527,6 +534,47 @@ export class IntegrationService implements Disposable {
await import(/* webpackChunkName: "integrations" */ './providers/gitlab')
).GitLabIntegration(this.container, this.authenticationService, this.getProvidersApi.bind(this));
break;
case SelfHostedIntegrationId.CloudGitLabSelfHosted:
if (domain == null) {
integration = this.findCachedById(id);
if (integration != null) {
// return immediately in order to not to cache it after the "switch" block:
return integration;
}

const existingConfigured = this.authenticationService.configured?.get(
SelfHostedIntegrationId.CloudGitLabSelfHosted,
);
if (existingConfigured?.length) {
const { domain: configuredDomain } = existingConfigured[0];
if (configuredDomain == null) throw new Error(`Domain is required for '${id}' integration`);
integration = new (
await import(/* webpackChunkName: "integrations" */ './providers/gitlab')
).GitLabSelfHostedIntegration(
this.container,
this.authenticationService,
this.getProvidersApi.bind(this),
configuredDomain,
id,
);
// assign domain because it's part of caching key:
domain = configuredDomain;
break;
}

throw new Error(`Domain is required for '${id}' integration`);
}

integration = new (
await import(/* webpackChunkName: "integrations" */ './providers/gitlab')
).GitLabSelfHostedIntegration(
this.container,
this.authenticationService,
this.getProvidersApi.bind(this),
domain,
id,
);
break;
case SelfHostedIntegrationId.GitLabSelfHosted:
if (domain == null) throw new Error(`Domain is required for '${id}' integration`);
integration = new (
Expand All @@ -536,6 +584,7 @@ export class IntegrationService implements Disposable {
this.authenticationService,
this.getProvidersApi.bind(this),
domain,
id,
);
break;
case HostingIntegrationId.Bitbucket:
Expand Down Expand Up @@ -630,8 +679,13 @@ export class IntegrationService implements Disposable {
}
return get(HostingIntegrationId.GitHub) as RT;
case 'gitlab':
if (remote.provider.custom && remote.provider.domain != null) {
return get(SelfHostedIntegrationId.GitLabSelfHosted, remote.provider.domain) as RT;
if (remote.provider.domain != null && !isGitLabDotCom(remote.provider.domain)) {
return get(
remote.provider.custom
? SelfHostedIntegrationId.GitLabSelfHosted
: SelfHostedIntegrationId.CloudGitLabSelfHosted,
remote.provider.domain,
) as RT;
}
return get(HostingIntegrationId.GitLab) as RT;
default:
Expand Down Expand Up @@ -771,9 +825,25 @@ export class IntegrationService implements Disposable {
args: { 0: integrationIds => (integrationIds?.length ? integrationIds.join(',') : '<undefined>') },
})
async getMyCurrentAccounts(
integrationIds: (HostingIntegrationId | SelfHostedIntegrationId.CloudGitHubEnterprise)[],
): Promise<Map<HostingIntegrationId | SelfHostedIntegrationId.CloudGitHubEnterprise, Account>> {
const accounts = new Map<HostingIntegrationId | SelfHostedIntegrationId.CloudGitHubEnterprise, Account>();
integrationIds: (
| HostingIntegrationId
| SelfHostedIntegrationId.CloudGitHubEnterprise
| SelfHostedIntegrationId.CloudGitLabSelfHosted
)[],
): Promise<
Map<
| HostingIntegrationId
| SelfHostedIntegrationId.CloudGitHubEnterprise
| SelfHostedIntegrationId.CloudGitLabSelfHosted,
Account
>
> {
const accounts = new Map<
| HostingIntegrationId
| SelfHostedIntegrationId.CloudGitHubEnterprise
| SelfHostedIntegrationId.CloudGitLabSelfHosted,
Account
>();
await Promise.allSettled(
integrationIds.map(async integrationId => {
const integration = await this.get(integrationId);
Expand All @@ -792,7 +862,11 @@ export class IntegrationService implements Disposable {
args: { 0: integrationIds => (integrationIds?.length ? integrationIds.join(',') : '<undefined>'), 1: false },
})
async getMyPullRequests(
integrationIds?: (HostingIntegrationId | SelfHostedIntegrationId.CloudGitHubEnterprise)[],
integrationIds?: (
| HostingIntegrationId
| SelfHostedIntegrationId.CloudGitHubEnterprise
| SelfHostedIntegrationId.CloudGitLabSelfHosted
)[],
cancellation?: CancellationToken,
silent?: boolean,
): Promise<IntegrationResult<SearchedPullRequest[] | undefined>> {
Expand Down
Loading

0 comments on commit 9a2f123

Please sign in to comment.