diff --git a/src/sql/platform/connection/common/connectionConfig.ts b/src/sql/platform/connection/common/connectionConfig.ts index 5c0d6843ca..7b8d23cfd5 100644 --- a/src/sql/platform/connection/common/connectionConfig.ts +++ b/src/sql/platform/connection/common/connectionConfig.ts @@ -7,7 +7,7 @@ import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilit import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants'; -import { IConnectionProfile, IConnectionProfileStore } from 'sql/platform/connection/common/interfaces'; +import { IConnectionProfile, IConnectionProfileStore, ProfileMatcher } from 'sql/platform/connection/common/interfaces'; import * as Utils from 'sql/platform/connection/common/utils'; import { generateUuid } from 'vs/base/common/uuid'; import * as nls from 'vs/nls'; @@ -61,7 +61,7 @@ export class ConnectionConfig { /** * Add a new connection to the connection config. */ - public addConnection(profile: IConnectionProfile): Promise { + public addConnection(profile: IConnectionProfile, matcher: ProfileMatcher = ConnectionProfile.matchesProfile): Promise { if (profile.saveProfile) { return this.addGroupFromProfile(profile).then(groupId => { let profiles = deepClone(this.configurationService.inspect(CONNECTIONS_CONFIG_KEY).userValue); @@ -75,7 +75,7 @@ export class ConnectionConfig { // Remove the profile if already set let sameProfileInList = find(profiles, value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); - return providerConnectionProfile.matches(connectionProfile); + return matcher(providerConnectionProfile, connectionProfile); }); if (sameProfileInList) { let profileIndex = firstIndex(profiles, value => value === sameProfileInList); diff --git a/src/sql/platform/connection/common/connectionManagement.ts b/src/sql/platform/connection/common/connectionManagement.ts index 7c980a8d43..519eb52dae 100644 --- a/src/sql/platform/connection/common/connectionManagement.ts +++ b/src/sql/platform/connection/common/connectionManagement.ts @@ -77,6 +77,11 @@ export interface IConnectionManagementService { // Properties providerNameToDisplayNameMap: { [providerDisplayName: string]: string }; + /** + * Opens the edit connection dialog to change connection. + */ + showEditConnectionDialog(model: IConnectionProfile): Promise; + /** * Opens the connection dialog to create new connection */ @@ -300,6 +305,7 @@ export interface INewConnectionParams { querySelection?: azdata.ISelectionData; showDashboard?: boolean; providers?: string[]; + isEditConnection?: boolean; } export interface IConnectableInput { diff --git a/src/sql/platform/connection/common/connectionProfile.ts b/src/sql/platform/connection/common/connectionProfile.ts index 8f8aafd5f4..696434270a 100644 --- a/src/sql/platform/connection/common/connectionProfile.ts +++ b/src/sql/platform/connection/common/connectionProfile.ts @@ -69,18 +69,23 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa this.options['databaseDisplayName'] = this.databaseName; } - public matches(other: interfaces.IConnectionProfile): boolean { - return other - && this.providerName === other.providerName - && this.nullCheckEqualsIgnoreCase(this.serverName, other.serverName) - && this.nullCheckEqualsIgnoreCase(this.databaseName, other.databaseName) - && this.nullCheckEqualsIgnoreCase(this.userName, other.userName) - && this.nullCheckEqualsIgnoreCase(this.options['databaseDisplayName'], other.options['databaseDisplayName']) - && this.authenticationType === other.authenticationType - && this.groupId === other.groupId; + public static matchesProfile(a: interfaces.IConnectionProfile, b: interfaces.IConnectionProfile): boolean { + return a && b + && a.providerName === b.providerName + && ConnectionProfile.nullCheckEqualsIgnoreCase(a.serverName, b.serverName) + && ConnectionProfile.nullCheckEqualsIgnoreCase(a.databaseName, b.databaseName) + && ConnectionProfile.nullCheckEqualsIgnoreCase(a.userName, b.userName) + && ConnectionProfile.nullCheckEqualsIgnoreCase(a.options['databaseDisplayName'], b.options['databaseDisplayName']) + && a.authenticationType === b.authenticationType + && a.groupId === b.groupId; } - private nullCheckEqualsIgnoreCase(a: string, b: string) { + public matches(other: interfaces.IConnectionProfile): boolean { + return ConnectionProfile.matchesProfile(this, other); + + } + + private static nullCheckEqualsIgnoreCase(a: string, b: string) { let bothNull: boolean = !a && !b; return bothNull ? bothNull : equalsIgnoreCase(a, b); } diff --git a/src/sql/platform/connection/common/connectionStore.ts b/src/sql/platform/connection/common/connectionStore.ts index 9d65e6e0c1..9e4645a567 100644 --- a/src/sql/platform/connection/common/connectionStore.ts +++ b/src/sql/platform/connection/common/connectionStore.ts @@ -9,7 +9,7 @@ import { ConnectionConfig } from 'sql/platform/connection/common/connectionConfi import { fixupConnectionCredentials } from 'sql/platform/connection/common/connectionInfo'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; -import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; +import { IConnectionProfile, ProfileMatcher } from 'sql/platform/connection/common/interfaces'; import { ICredentialsService } from 'sql/platform/credentials/common/credentialsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -103,21 +103,18 @@ export class ConnectionStore { * @param whether the plaintext password should be written to the settings file * @returns a Promise that returns the original profile, for help in chaining calls */ - public saveProfile(profile: IConnectionProfile, forceWritePlaintextPassword?: boolean): Promise { + public async saveProfile(profile: IConnectionProfile, forceWritePlaintextPassword?: boolean, matcher?: ProfileMatcher): Promise { // Add the profile to the saved list, taking care to clear out the password field if necessary const savedProfile = forceWritePlaintextPassword ? profile : this.getProfileWithoutPassword(profile); - return this.saveProfileToConfig(savedProfile) - .then(savedConnectionProfile => { - profile.groupId = savedConnectionProfile.groupId; - profile.id = savedConnectionProfile.id; - // Only save if we successfully added the profile - return this.saveProfilePasswordIfNeeded(profile); - }).then(() => { - // Add necessary default properties before returning - // this is needed to support immediate connections - fixupConnectionCredentials(profile); - return profile; - }); + const savedConnectionProfile = await this.saveProfileToConfig(savedProfile, matcher); + profile.groupId = savedConnectionProfile.groupId; + profile.id = savedConnectionProfile.id; + // Only save if we successfully added the profile + await this.saveProfilePasswordIfNeeded(profile); + // Add necessary default properties before returning + // this is needed to support immediate connections + fixupConnectionCredentials(profile); + return profile; } public savePassword(profile: IConnectionProfile): Promise { @@ -134,9 +131,9 @@ export class ConnectionStore { return this.connectionConfig.addGroup(profile); } - private saveProfileToConfig(profile: IConnectionProfile): Promise { + private saveProfileToConfig(profile: IConnectionProfile, matcher?: ProfileMatcher): Promise { if (profile.saveProfile) { - return this.connectionConfig.addConnection(profile); + return this.connectionConfig.addConnection(profile, matcher); } else { return Promise.resolve(profile); } diff --git a/src/sql/platform/connection/common/interfaces.ts b/src/sql/platform/connection/common/interfaces.ts index 42eec4b501..9b1786963e 100644 --- a/src/sql/platform/connection/common/interfaces.ts +++ b/src/sql/platform/connection/common/interfaces.ts @@ -5,6 +5,9 @@ import * as azdata from 'azdata'; +// Used to allow various methods of matching profiles +export type ProfileMatcher = (a: IConnectionProfile, b: IConnectionProfile) => boolean; + export interface IConnectionProfile extends azdata.IConnectionProfile { getOptionsKey(): string; matches(profile: azdata.IConnectionProfile): boolean; diff --git a/src/sql/platform/connection/test/common/testConnectionManagementService.ts b/src/sql/platform/connection/test/common/testConnectionManagementService.ts index 5a3a9187a4..2a358c79d9 100644 --- a/src/sql/platform/connection/test/common/testConnectionManagementService.ts +++ b/src/sql/platform/connection/test/common/testConnectionManagementService.ts @@ -59,6 +59,10 @@ export class TestConnectionManagementService implements IConnectionManagementSer return undefined!; } + showEditConnectionDialog(model: IConnectionProfile): Promise { + return undefined!; + } + onConnectionComplete(handle: number, connectionInfoSummary: azdata.ConnectionInfoSummary): void { } diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts index 85c7fc5bf1..d4e81e9d14 100644 --- a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { - RefreshAction, AddServerAction, DeleteConnectionAction, DisconnectConnectionAction, + RefreshAction, EditConnectionAction, AddServerAction, DeleteConnectionAction, DisconnectConnectionAction, ActiveConnectionsFilterAction, RecentConnectionsFilterAction } from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction'; @@ -68,7 +68,7 @@ suite('SQL Connection Tree Action tests', () => { connectionManagementService.setup(x => x.deleteConnectionGroup(TypeMoq.It.isAny())).returns(() => Promise.resolve(true)); connectionManagementService.setup(x => x.deleteConnection(TypeMoq.It.isAny())).returns(() => Promise.resolve(true)); connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => profileToReturn); - + connectionManagementService.setup(x => x.showEditConnectionDialog(TypeMoq.It.isAny())).returns(() => new Promise((resolve, reject) => resolve())); return connectionManagementService; } @@ -527,4 +527,32 @@ suite('SQL Connection Tree Action tests', () => { }); }); + test('EditConnectionAction - test if show connection dialog is called', () => { + let connectionManagementService = createConnectionManagementService(true, undefined); + + let connection: ConnectionProfile = new ConnectionProfile(capabilitiesService, { + connectionName: 'Test', + savePassword: false, + groupFullName: 'testGroup', + serverName: 'testServerName', + databaseName: 'testDatabaseName', + authenticationType: 'integrated', + password: 'test', + userName: 'testUsername', + groupId: undefined, + providerName: mssqlProviderName, + options: {}, + saveProfile: true, + id: 'testId' + }); + + let connectionAction: EditConnectionAction = new EditConnectionAction(EditConnectionAction.ID, + EditConnectionAction.LABEL, + connection, + connectionManagementService.object); + + return connectionAction.run().then((value) => { + connectionManagementService.verify(x => x.showEditConnectionDialog(TypeMoq.It.isAny()), TypeMoq.Times.once()); + }); + }); }); diff --git a/src/sql/workbench/services/connection/browser/connectionDialogService.ts b/src/sql/workbench/services/connection/browser/connectionDialogService.ts index 15d3711e3f..bbb7dadfe2 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogService.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogService.ts @@ -89,7 +89,7 @@ export class ConnectionDialogService implements IConnectionDialogService { @IConfigurationService private _configurationService: IConfigurationService, @IClipboardService private _clipboardService: IClipboardService, @ICommandService private _commandService: ICommandService, - @ILogService private _logService: ILogService + @ILogService private _logService: ILogService, ) { this.initializeConnectionProviders(); } @@ -355,6 +355,7 @@ export class ConnectionDialogService implements IConnectionDialogService { } private updateModelServerCapabilities(model: IConnectionProfile) { + if (this._model) { this._model.dispose(); } @@ -410,32 +411,32 @@ export class ConnectionDialogService implements IConnectionDialogService { return this._dialogDeferredPromise.promise; } - public showDialog( + public async showDialog( connectionManagementService: IConnectionManagementService, params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult, - connectionOptions?: IConnectionCompletionOptions): Promise { + connectionOptions?: IConnectionCompletionOptions, + ): Promise { this._connectionManagementService = connectionManagementService; this._options = connectionOptions; this._params = params; this._inputModel = model; - return new Promise((resolve, reject) => { - this.updateModelServerCapabilities(model); - // If connecting from a query editor set "save connection" to false - if (params && (params.input && params.connectionType === ConnectionType.editor || - params.connectionType === ConnectionType.temporary)) { - this._model.saveProfile = false; - } - resolve(this.showDialogWithModel().then(() => { - if (connectionResult && connectionResult.errorMessage) { - this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.callStack); - } - })); - }); + this.updateModelServerCapabilities(model); + + // If connecting from a query editor set "save connection" to false + if (params && (params.input && params.connectionType === ConnectionType.editor || + params.connectionType === ConnectionType.temporary)) { + this._model.saveProfile = false; + } + await this.showDialogWithModel(); + + if (connectionResult && connectionResult.errorMessage) { + this.showErrorDialog(Severity.Error, this._connectionErrorTitle, connectionResult.errorMessage, connectionResult.callStack); + } } private async doShowDialog(params: INewConnectionParams): Promise { diff --git a/src/sql/workbench/services/connection/browser/connectionManagementService.ts b/src/sql/workbench/services/connection/browser/connectionManagementService.ts index 952b1aa3e8..8a6911a544 100644 --- a/src/sql/workbench/services/connection/browser/connectionManagementService.ts +++ b/src/sql/workbench/services/connection/browser/connectionManagementService.ts @@ -225,6 +225,26 @@ export class ConnectionManagementService extends Disposable implements IConnecti }); } + /** + * Opens the edit connection dialog + * @param model the existing connection profile to edit on. + */ + public async showEditConnectionDialog(model: interfaces.IConnectionProfile): Promise { + if (!model) { + throw new Error('Connection Profile is undefined'); + } + let params = { + connectionType: ConnectionType.default, + isEditConnection: true + }; + + try { + return await this._connectionDialogService.showDialog(this, params, model); + } catch (dialogError) { + this._logService.warn('failed to open the connection dialog. error: ' + dialogError); + } + } + /** * Load the password for the profile * @param connectionProfile Connection Profile @@ -420,6 +440,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti connection.options['groupId'] = connection.groupId; connection.options['databaseDisplayName'] = connection.databaseName; + let isEdit = options?.params?.isEditConnection ?? false; + if (!uri) { uri = Utils.generateUri(connection); } @@ -449,6 +471,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti if (!tokenFillSuccess) { throw new Error(nls.localize('connection.noAzureAccount', "Failed to get Azure account token for connection")); } + return this.createNewConnection(uri, connection).then(async connectionResult => { if (connectionResult && connectionResult.connected) { // The connected succeeded so add it to our active connections now, optionally adding it to the MRU based on @@ -459,8 +482,15 @@ export class ConnectionManagementService extends Disposable implements IConnecti if (callbacks.onConnectSuccess) { callbacks.onConnectSuccess(options.params, connectionResult.connectionProfile); } - if (options.saveTheConnection) { - await this.saveToSettings(uri, connection).then(value => { + if (options.saveTheConnection || isEdit) { + let matcher: interfaces.ProfileMatcher; + if (isEdit) { + matcher = (a: interfaces.IConnectionProfile, b: interfaces.IConnectionProfile) => { + return a.id === b.id; + }; + } + + await this.saveToSettings(uri, connection, matcher).then(value => { this._onAddConnectionProfile.fire(connection); this.doActionsAfterConnectionComplete(value, options); }); @@ -496,6 +526,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti }); } + private handleConnectionError(connection: interfaces.IConnectionProfile, uri: string, options: IConnectionCompletionOptions, callbacks: IConnectionCallbacks, connectionResult: IConnectionResult) { let connectionNotAcceptedError = nls.localize('connectionNotAcceptedError', "Connection Not Accepted"); if (options.showFirewallRuleOnError && connectionResult.errorCode) { @@ -529,7 +560,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti }); } - private doActionsAfterConnectionComplete(uri: string, options: IConnectionCompletionOptions,) { + private doActionsAfterConnectionComplete(uri: string, options: IConnectionCompletionOptions): void { let connectionManagementInfo = this._connectionStatusManager.findConnection(uri); if (options.showDashboard) { this.showDashboardForConnectionManagementInfo(connectionManagementInfo.connectionProfile); @@ -873,11 +904,10 @@ export class ConnectionManagementService extends Disposable implements IConnecti }); } - private saveToSettings(id: string, connection: interfaces.IConnectionProfile): Promise { - return this._connectionStore.saveProfile(connection).then(savedProfile => { - let newId = this._connectionStatusManager.updateConnectionProfile(savedProfile, id); - return newId; - }); + + private async saveToSettings(id: string, connection: interfaces.IConnectionProfile, matcher?: interfaces.ProfileMatcher): Promise { + const savedProfile = await this._connectionStore.saveProfile(connection, undefined, matcher); + return this._connectionStatusManager.updateConnectionProfile(savedProfile, id); } /** diff --git a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts index 254e627d0f..b31a55ba51 100644 --- a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts +++ b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts @@ -102,7 +102,7 @@ suite('SQL ConnectionManagementService tests', () => { connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(none)); connectionStore.setup(x => x.addRecentConnection(TypeMoq.It.isAny())).returns(() => Promise.resolve()); - connectionStore.setup(x => x.saveProfile(TypeMoq.It.isAny())).returns(() => Promise.resolve(connectionProfile)); + connectionStore.setup(x => x.saveProfile(TypeMoq.It.isAny(), TypeMoq.It.is(x => true), TypeMoq.It.is(x => true))).returns(() => Promise.resolve(connectionProfile)); workbenchEditorService.setup(x => x.openEditor(undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is( c => c.serverName === connectionProfile.serverName))).returns(() => Promise.resolve({ profile: connectionProfile, savedCred: true })); @@ -204,7 +204,7 @@ suite('SQL ConnectionManagementService tests', () => { if (options) { if (options.saveTheConnection) { - connectionStore.verify(x => x.saveProfile(TypeMoq.It.isAny()), TypeMoq.Times.once()); + connectionStore.verify(x => x.saveProfile(TypeMoq.It.isAny(), TypeMoq.It.is(x => true), TypeMoq.It.is(x => true)), TypeMoq.Times.once()); } if (options.showDashboard) { workbenchEditorService.verify(x => x.openEditor(undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); @@ -358,7 +358,7 @@ suite('SQL ConnectionManagementService tests', () => { showFirewallRuleOnError: true }; - return connect(uri, options).then(() => { + return connect(uri, options).then((result) => { verifyOptions(options); assert.notEqual(paramsInOnConnectSuccess, undefined); assert.equal(paramsInOnConnectSuccess.connectionType, options.params.connectionType); @@ -441,6 +441,82 @@ suite('SQL ConnectionManagementService tests', () => { }); }); + test('Edit Connection - Changing connection profile name for same URI should persist after edit', () => { + let profile = connectionProfile; + let uri1 = 'test_uri1'; + let newname = 'connection renamed'; + let options: IConnectionCompletionOptions = { + params: { + connectionType: ConnectionType.editor, + input: { + onConnectSuccess: undefined, + onConnectReject: undefined, + onConnectStart: undefined, + onDisconnect: undefined, + onConnectCanceled: undefined, + uri: uri1, + }, + querySelection: undefined, + runQueryOnCompletion: RunQueryOnConnectionMode.none, + isEditConnection: false + }, + saveTheConnection: true, + showDashboard: false, + showConnectionDialogOnError: true, + showFirewallRuleOnError: true + }; + + return connect(uri1, options, true, profile).then(result => { + assert.equal(result.connected, true); + let newProfile = connectionProfile; + newProfile.connectionName = newname; + options.params.isEditConnection = true; + return connect(uri1, options, true, profile).then(result => { + assert.equal(result.connected, true); + assert.equal(connectionManagementService.getConnectionProfile(uri1).connectionName, newname); + }); + }); + }); + + test('Edit Connection - Connecting a different URI with same profile via edit should not change profile ID.', () => { + let profile = connectionProfile; + profile.id = '0451'; + let uri1 = 'test_uri1'; + let uri2 = 'test_uri2'; + let options: IConnectionCompletionOptions = { + params: { + connectionType: ConnectionType.editor, + input: { + onConnectSuccess: undefined, + onConnectReject: undefined, + onConnectStart: undefined, + onDisconnect: undefined, + onConnectCanceled: undefined, + uri: uri1 + }, + querySelection: undefined, + runQueryOnCompletion: RunQueryOnConnectionMode.none, + isEditConnection: false + }, + saveTheConnection: true, + showDashboard: false, + showConnectionDialogOnError: true, + showFirewallRuleOnError: true + }; + + return connect(uri1, options, true, profile).then(result => { + assert.equal(result.connected, true); + options.params.isEditConnection = true; + return connect(uri2, options, true, profile).then(result => { + assert.equal(result.connected, true); + let uri1info = connectionManagementService.getConnectionInfo(uri1); + let uri2info = connectionManagementService.getConnectionInfo(uri2); + assert.equal(uri1info.connectionProfile.id, uri2info.connectionProfile.id); + }); + }); + }); + + test('failed firewall rule should open the firewall rule dialog', () => { handleFirewallRuleResult.canHandleFirewallRule = true; resolveHandleFirewallRuleDialog = true; diff --git a/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts b/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts index 195d4adb2d..1c81aab1f0 100644 --- a/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts +++ b/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts @@ -83,6 +83,30 @@ export class RefreshAction extends Action { } } +export class EditConnectionAction extends Action { + public static ID = 'registeredServers.editConnection'; + public static LABEL = localize('connectionTree.editConnection', "Edit Connection"); + + constructor( + id: string, + label: string, + private _connectionProfile: ConnectionProfile, + @IConnectionManagementService private _connectionManagementService: IConnectionManagementService + ) { + super(id, label); + this.class = 'edit-server-action'; + } + + public async run(): Promise { + if (!this._connectionProfile) { + return false; + } + + await this._connectionManagementService.showEditConnectionDialog(this._connectionProfile); + return true; + } +} + export class DisconnectConnectionAction extends Action { public static ID = 'objectExplorer.disconnect'; public static LABEL = localize('DisconnectAction', "Disconnect"); diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts index 160f59f225..0fe0727a8c 100644 --- a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts +++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts @@ -9,7 +9,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { - DisconnectConnectionAction, AddServerAction, + DisconnectConnectionAction, AddServerAction, EditConnectionAction, DeleteConnectionAction, RefreshAction, EditServerGroupAction } from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction'; import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode'; @@ -129,6 +129,7 @@ export class ServerTreeActionProvider { if (this._connectionManagementService.isProfileConnected(context.profile)) { actions.push(this._instantiationService.createInstance(DisconnectConnectionAction, DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL, context.profile)); } + actions.push(this._instantiationService.createInstance(EditConnectionAction, EditConnectionAction.ID, EditConnectionAction.LABEL, context.profile)); actions.push(this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, context.profile)); // Contribute refresh action for scriptable objects via contribution