From 97ea1d981c5dbf462d5ae1bb68fa8a3a464c1b96 Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Wed, 24 Sep 2025 14:23:49 +0200 Subject: [PATCH] Add Linear to autolink settings (#4605, #4613) --- .../integrations/authentication/models.ts | 5 +++ src/webviews/apps/settings/settings.ts | 40 +++++++++++++++---- src/webviews/settings/protocol.ts | 13 +++--- src/webviews/settings/settingsWebview.ts | 25 ++++++++++-- 4 files changed, 65 insertions(+), 18 deletions(-) diff --git a/src/plus/integrations/authentication/models.ts b/src/plus/integrations/authentication/models.ts index 10bbfe8ee68f8..47d0cbd8b1f15 100644 --- a/src/plus/integrations/authentication/models.ts +++ b/src/plus/integrations/authentication/models.ts @@ -69,6 +69,11 @@ export function isSupportedCloudIntegrationId(id: string): id is SupportedCloudI return getSupportedCloudIntegrationIds().includes(id as SupportedCloudIntegrationIds); } +export function isIssueCloudIntegrationId(id: string): id is IssuesCloudHostIntegrationId { + const issueIds: string[] = Object.values(IssuesCloudHostIntegrationId); + return issueIds.includes(id); +} + export const toIntegrationId: { [key in CloudIntegrationType]: IntegrationIds } = { jira: IssuesCloudHostIntegrationId.Jira, linear: IssuesCloudHostIntegrationId.Linear, diff --git a/src/webviews/apps/settings/settings.ts b/src/webviews/apps/settings/settings.ts index f4659ec37ad1f..008f95938f92e 100644 --- a/src/webviews/apps/settings/settings.ts +++ b/src/webviews/apps/settings/settings.ts @@ -2,14 +2,15 @@ import './settings.scss'; import type { ConnectCloudIntegrationsCommandArgs } from '../../../commands/cloudIntegrations'; import type { AutolinkConfig } from '../../../config'; -import type { IssuesCloudHostIntegrationId, SupportedCloudIntegrationIds } from '../../../constants.integrations'; +import type { SupportedCloudIntegrationIds } from '../../../constants.integrations'; +import { IssuesCloudHostIntegrationId } from '../../../constants.integrations'; import { createCommandLink } from '../../../system/commands'; import type { IpcMessage, UpdateConfigurationParams } from '../../protocol'; import { DidChangeConfigurationNotification, UpdateConfigurationCommand } from '../../protocol'; import type { State } from '../../settings/protocol'; import { DidChangeAccountNotification, - DidChangeConnectedJiraNotification, + DidChangeIssueIntegrationConnectedNotification, DidOpenAnchorNotification, GenerateConfigurationPreviewRequest, } from '../../settings/protocol'; @@ -159,8 +160,12 @@ export class SettingsApp extends App { this.renderAutolinkIntegration(); break; - case DidChangeConnectedJiraNotification.is(msg): - this.state.hasConnectedJira = msg.params.hasConnectedJira; + case DidChangeIssueIntegrationConnectedNotification.is(msg): + if (msg.params.integrationId === IssuesCloudHostIntegrationId.Jira) { + this.state.hasConnectedJira = msg.params.connected; + } else if (msg.params.integrationId === IssuesCloudHostIntegrationId.Linear) { + this.state.hasConnectedLinear = msg.params.connected; + } this.setState(this.state); this.renderAutolinkIntegration(); break; @@ -804,8 +809,8 @@ export class SettingsApp extends App { const $root = document.querySelector('[data-component="autolink-integration"]'); if ($root == null) return; - const { hasAccount, hasConnectedJira } = this.state; - let message = `( 'gitlens.plus.cloudIntegrations.connect', { integrationIds: ['jira' as IssuesCloudHostIntegrationId.Jira] as SupportedCloudIntegrationIds[], @@ -821,11 +826,30 @@ export class SettingsApp extends App { hasAccount ? '' : 'sign up and ' }get access to automatic rich Jira autolinks.`; if (hasAccount && hasConnectedJira) { - message = + messageJira = ' Jira connected — automatic rich Jira autolinks are enabled.'; } + let messageLinear = `Connect to Linear — ${ + hasAccount ? '' : 'sign up and ' + }get access to automatic rich Linear autolinks.`; + if (hasAccount && hasConnectedLinear) { + messageLinear = + ' Linear connected — automatic rich Linear autolinks are enabled.'; + } - $root.innerHTML = message; + $root.innerHTML = `${messageJira}
${messageLinear}`; } private renderAutolinks() { diff --git a/src/webviews/settings/protocol.ts b/src/webviews/settings/protocol.ts index 5229d3b8cb76f..865c903756abd 100644 --- a/src/webviews/settings/protocol.ts +++ b/src/webviews/settings/protocol.ts @@ -1,4 +1,5 @@ import type { Config } from '../../config'; +import type { IssuesCloudHostIntegrationId } from '../../constants.integrations'; import type { IpcScope, WebviewState } from '../protocol'; import { IpcNotification, IpcRequest } from '../protocol'; @@ -12,6 +13,7 @@ export interface State extends WebviewState { scopes: ['user' | 'workspace', string][]; hasAccount: boolean; hasConnectedJira: boolean; + hasConnectedLinear: boolean; } // REQUESTS @@ -43,10 +45,9 @@ export interface DidChangeAccountParams { } export const DidChangeAccountNotification = new IpcNotification(scope, 'didChangeAccount'); -export interface DidChangeConnectedJiraParams { - hasConnectedJira: boolean; +export interface DidChangeIssueIntegrationConnectedParams { + integrationId: IssuesCloudHostIntegrationId; + connected: boolean; } -export const DidChangeConnectedJiraNotification = new IpcNotification( - scope, - 'didChangeConnectedJira', -); +export const DidChangeIssueIntegrationConnectedNotification = + new IpcNotification(scope, 'didChangeIssueIntegrationConnected'); diff --git a/src/webviews/settings/settingsWebview.ts b/src/webviews/settings/settingsWebview.ts index df4f79e220c0f..82be14a63c520 100644 --- a/src/webviews/settings/settingsWebview.ts +++ b/src/webviews/settings/settingsWebview.ts @@ -10,10 +10,12 @@ import { GitFileChange } from '../../git/models/fileChange'; import { GitFileIndexStatus } from '../../git/models/fileStatus'; import { PullRequest } from '../../git/models/pullRequest'; import type { SubscriptionChangeEvent } from '../../plus/gk/subscriptionService'; +import { isIssueCloudIntegrationId } from '../../plus/integrations/authentication/models'; import type { ConnectionStateChangeEvent } from '../../plus/integrations/integrationService'; import type { ConfigPath, CoreConfigPath } from '../../system/-webview/configuration'; import { configuration } from '../../system/-webview/configuration'; import { map } from '../../system/iterable'; +import { getSettledValue } from '../../system/promise'; import type { CustomConfigPath, IpcMessage } from '../protocol'; import { assertsConfigKeyValue, @@ -25,7 +27,7 @@ import type { WebviewHost, WebviewProvider } from '../webviewProvider'; import type { State } from './protocol'; import { DidChangeAccountNotification, - DidChangeConnectedJiraNotification, + DidChangeIssueIntegrationConnectedNotification, DidOpenAnchorNotification, GenerateConfigurationPreviewRequest, } from './protocol'; @@ -61,8 +63,12 @@ export class SettingsWebviewProvider implements WebviewProvider { + const linear = await this.container.integrations.get(IssuesCloudHostIntegrationId.Linear); + if (linear == null) return false; + return linear.maybeConnected ?? linear.isConnected(); + } + async includeBootstrap(): Promise { const scopes: ['user' | 'workspace', string][] = [['user', 'User']]; if (workspace.workspaceFolders?.length) { scopes.push(['workspace', 'Workspace']); } + const [jiraConnected, linearConnected] = ( + await Promise.allSettled([this.getJiraConnected(), this.getLinearConnected()]) + ).map(r => getSettledValue(r, false)); + return { ...this.host.baseWebviewState, version: this.container.version, @@ -91,7 +107,8 @@ export class SettingsWebviewProvider implements WebviewProvider