diff --git a/src/sql/platform/connection/common/connectionManagement.ts b/src/sql/platform/connection/common/connectionManagement.ts index 77ec582292..6c35a2c64d 100644 --- a/src/sql/platform/connection/common/connectionManagement.ts +++ b/src/sql/platform/connection/common/connectionManagement.ts @@ -85,17 +85,6 @@ export const SERVICE_ID = 'connectionManagementService'; export const IConnectionManagementService = createDecorator(SERVICE_ID); -export interface ConnectionElementMovedParams { - source: ConnectionProfile | ConnectionProfileGroup; - oldGroupId: string; - newGroupId: string; -} - -export interface ConnectionProfileEditedParams { - profile: ConnectionProfile; - oldProfileId: string; -} - export interface IConnectionManagementService { _serviceBrand: undefined; @@ -107,25 +96,6 @@ export interface IConnectionManagementService { onConnectionChanged: Event; onLanguageFlavorChanged: Event; - // Event Emitters for async tree - /** - * Connection Profile events. - */ - onConnectionProfileCreated: Event; - onConnectionProfileEdited: Event; - onConnectionProfileDeleted: Event; - onConnectionProfileMoved: Event; - onConnectionProfileConnected: Event; - onConnectionProfileDisconnected: Event; - /** - * Connection Profile Group events. - */ - onConnectionProfileGroupCreated: Event; - onConnectionProfileGroupEdited: Event; - onConnectionProfileGroupDeleted: Event; - onConnectionProfileGroupMoved: Event; - // End of Event Emitters for async tree - // Properties providerNameToDisplayNameMap: { [providerDisplayName: string]: string }; @@ -189,8 +159,6 @@ export interface IConnectionManagementService { getConnectionGroups(providers?: string[]): ConnectionProfileGroup[]; - getConnectionGroupById(id: string): ConnectionProfileGroup | undefined; - getRecentConnections(providers?: string[]): ConnectionProfile[]; clearRecentConnectionsList(): void; diff --git a/src/sql/platform/connection/common/connectionProfileGroup.ts b/src/sql/platform/connection/common/connectionProfileGroup.ts index 888c4e9ab8..8616b26bdf 100644 --- a/src/sql/platform/connection/common/connectionProfileGroup.ts +++ b/src/sql/platform/connection/common/connectionProfileGroup.ts @@ -126,8 +126,6 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro public getChildren(): (ConnectionProfile | ConnectionProfileGroup)[] { let allChildren: (ConnectionProfile | ConnectionProfileGroup)[] = []; this._childConnections.forEach((conn) => { - conn.parent = this; - conn.groupId = this.id; allChildren.push(conn); }); @@ -236,8 +234,4 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro } return subgroups; } - - public static createConnectionProfileGroup(group: IConnectionProfileGroup, parentGroup: ConnectionProfileGroup | undefined): ConnectionProfileGroup { - return new ConnectionProfileGroup(group.name, parentGroup, group.id, group.color, group.description); - } } diff --git a/src/sql/platform/connection/test/common/testConnectionManagementService.ts b/src/sql/platform/connection/test/common/testConnectionManagementService.ts index 6a6f46057e..c990ca6bc4 100644 --- a/src/sql/platform/connection/test/common/testConnectionManagementService.ts +++ b/src/sql/platform/connection/test/common/testConnectionManagementService.ts @@ -26,17 +26,6 @@ export class TestConnectionManagementService implements IConnectionManagementSer onDeleteConnectionProfile = undefined!; onLanguageFlavorChanged = undefined!; - public onConnectionProfileCreated: Event = Event.None; - public onConnectionProfileEdited: Event = Event.None; - public onConnectionProfileDeleted: Event = Event.None; - public onConnectionProfileMoved: Event = Event.None; - public onConnectionProfileConnected: Event = Event.None; - public onConnectionProfileDisconnected: Event = Event.None; - public onConnectionProfileGroupCreated: Event = Event.None; - public onConnectionProfileGroupEdited: Event = Event.None; - public onConnectionProfileGroupDeleted: Event = Event.None; - public onConnectionProfileGroupMoved: Event = Event.None; - public get onConnect(): Event { return Event.None; } @@ -101,10 +90,6 @@ export class TestConnectionManagementService implements IConnectionManagementSer return []; } - getConnectionGroupById(id: string): ConnectionProfileGroup | undefined { - return undefined; - } - getActiveConnections(providers?: string[]): ConnectionProfile[] { return []; } diff --git a/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts b/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts index fd2ebfc703..883c25f398 100644 --- a/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts +++ b/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts @@ -173,10 +173,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView { this._register(this._tree.onContextMenu(e => this.onTreeNodeContextMenu(e))); this._register(this._tree.onMouseDblClick(async e => { await this.onTreeNodeDoubleClick(e.element); })); this._register(this._connectionManagementService.onConnectionChanged(() => { - // No need to refresh AsyncServerTree when a connection is edited or added - if (!(this._tree instanceof AsyncServerTree)) { - this.refreshTree().catch(err => errors.onUnexpectedError); - } + this.refreshTree().catch(err => errors.onUnexpectedError); })); } @@ -188,19 +185,11 @@ export class ServerTreeView extends Disposable implements IServerTreeView { this.handleAddConnectionProfile(newProfile).catch(errors.onUnexpectedError); })); this._register(this._connectionManagementService.onDeleteConnectionProfile(() => { - // No need to refresh AsyncServerTree when a connection is deleted - if (!(this._tree instanceof AsyncServerTree)) { - this.refreshTree().catch(errors.onUnexpectedError); - } + this.refreshTree().catch(errors.onUnexpectedError); })); - - this._register(this._connectionManagementService.onDisconnect(async (connectionParams) => { - if (!(this._tree instanceof AsyncServerTree)) { - if (this.isObjectExplorerConnectionUri(connectionParams.connectionUri)) { - this.deleteObjectExplorerNodeAndRefreshTree(connectionParams.connectionProfile).catch(errors.onUnexpectedError); - } - } else { - await this.disconnectConnection(connectionParams.connectionProfile); + this._register(this._connectionManagementService.onDisconnect((connectionParams) => { + if (this.isObjectExplorerConnectionUri(connectionParams.connectionUri)) { + this.deleteObjectExplorerNodeAndRefreshTree(connectionParams.connectionProfile).catch(errors.onUnexpectedError); } })); this._register(this._configurationService.onDidChangeConfiguration(e => { @@ -213,163 +202,13 @@ export class ServerTreeView extends Disposable implements IServerTreeView { this._register(this._objectExplorerService.onUpdateObjectExplorerNodes(args => { if (args.errorMessage) { this.showError(args.errorMessage); - } else if (args.connection) { - if (this._tree instanceof AsyncServerTree) { - // Rerendering the node to update the badge - this._tree.rerender(args.connection); - } + } + if (args.connection) { this.onObjectExplorerSessionCreated(args.connection).catch(err => errors.onUnexpectedError); } })); } - // Add connection profile to parent group and update group children. Then reveal and expand the new connection - this._register(this._connectionManagementService.onConnectionProfileCreated(async (newConnection) => { - if (this._tree instanceof AsyncServerTree) { - const connectionParentGroup = this._tree.getElementById(newConnection.groupId) as ConnectionProfileGroup; - if (connectionParentGroup) { - connectionParentGroup.connections.push(newConnection); - newConnection.parent = connectionParentGroup; - newConnection.groupId = connectionParentGroup.id; - await this._tree.updateChildren(connectionParentGroup); - await this._tree.revealSelectFocusElement(newConnection); - await this._tree.expand(newConnection); - } - } - })); - - // Rerender the connection in the tree to update the badge and update the children of the connection. - this._register(this._connectionManagementService.onConnectionProfileConnected(async (connectedConnection) => { - if (this._tree instanceof AsyncServerTree) { - const connectionInTree = this._tree.getElementById(connectedConnection.id); - if (connectionInTree) { - await this._tree.rerender(connectionInTree); - await this._tree.revealSelectFocusElement(connectionInTree); - await this._tree.updateChildren(connectionInTree); - await this._tree.expand(connectionInTree); - } - } - })); - - // Remove the connection from the parent group and update the parent's children. - this._register(this._connectionManagementService.onConnectionProfileDeleted(async (e) => { - if (this._tree instanceof AsyncServerTree) { - const parentGroup = this._tree.getElementById(e.groupId); - if (parentGroup) { - parentGroup.connections = parentGroup.connections.filter(c => c.id !== e.id); - await this._tree.updateChildren(parentGroup); - await this._tree.revealSelectFocusElement(parentGroup); - } - } - })); - - - this._register(this._connectionManagementService.onConnectionProfileEdited(async (e) => { - if (this._tree instanceof AsyncServerTree) { - const oldProfile = this._tree.getElementById(e.oldProfileId); - const oldProfileParent = this._tree.getElementById(oldProfile.groupId); - if (oldProfileParent.id !== e.profile.groupId) { - // If the profile was moved to a different group then remove it from the old group and add it to the new group. - oldProfileParent.connections = oldProfileParent.connections.filter(c => c.id !== oldProfile.id); - await this._tree.updateChildren(oldProfileParent); - const newProfileParent = this._tree.getElementById(e.profile.groupId); - newProfileParent.connections.push(e.profile); - e.profile.parent = newProfileParent; - e.profile.groupId = newProfileParent.id; - await this._tree.updateChildren(newProfileParent); - await this._tree.revealSelectFocusElement(e.profile); - await this._tree.expand(e.profile); - } else { - // If the profile was not moved to a different group then just update the profile in the group. - oldProfileParent.connections[oldProfileParent.connections.findIndex(c => c.id === e.oldProfileId)] = e.profile; - e.profile.parent = oldProfileParent; - e.profile.groupId = oldProfileParent.id; - await this._tree.updateChildren(oldProfileParent) - await this._tree.revealSelectFocusElement(e.profile); - await this._tree.expand(e.profile); - } - } - })); - - this._register(this._connectionManagementService.onConnectionProfileMoved(async (e) => { - if (this._tree instanceof AsyncServerTree) { - const movedConnection = e.source; - const oldParent = this._tree.getElementById(e.oldGroupId); - const newParent = this._tree.getElementById(e.newGroupId); - // Storing the expanded state of children of the moved connection so that they can be expanded after the move. - const profileExpandedState = this._tree.getExpandedState(movedConnection); - if (oldParent) { - oldParent.connections = oldParent.connections.filter(c => c.id !== e.source.id); - await this._tree.updateChildren(oldParent); - } - if (newParent) { - newParent.connections.push(movedConnection); - movedConnection.parent = newParent; - movedConnection.groupId = newParent.id; - await this._tree.updateChildren(newParent); - } - const newConnection = this._tree.getElementById(movedConnection.id); - if (newConnection) { - await this._tree.revealSelectFocusElement(newConnection); - // Expanding the previously expanded children of the moved connection after the move. - await this._tree.expandElements(profileExpandedState); - } - } - })); - - this._register(this._connectionManagementService.onConnectionProfileGroupDeleted(async (e) => { - if (this._tree instanceof AsyncServerTree) { - const parent = this._tree.getElementById(e.parentId); - parent.children = parent.children.filter(c => c.id !== e.id); - await this._tree.updateChildren(parent); - await this._tree.revealSelectFocusElement(parent); - } - })); - - this._register(this._connectionManagementService.onConnectionProfileGroupCreated(async (e) => { - if (this._tree instanceof AsyncServerTree) { - let parent = this._tree.getElementById(e.parentId); - if (!parent) { - parent = this._tree.getInput(); // If the parent is not found then add the group to the root. - } - parent.children.push(e); - e.parent = parent; - e.parentId = parent.id; - await this._tree.updateChildren(parent); - await this._tree.revealSelectFocusElement(e); - } - })); - - this._register(this._connectionManagementService.onConnectionProfileGroupEdited(async (e) => { - if (this._tree instanceof AsyncServerTree) { - const newParent = this._tree.getElementById(e.parentId); - if (newParent) { - newParent.children[newParent.children.findIndex(c => c.id === e.id)] = e; - await this._tree.updateChildren(newParent); - await this._tree.revealSelectFocusElement(e); - } - } - })); - - this._register(this._connectionManagementService.onConnectionProfileGroupMoved(async (e) => { - if (this._tree instanceof AsyncServerTree) { - const movedGroup = e.source; - const oldParent = this._tree.getElementById(e.oldGroupId); - const newParent = this._tree.getElementById(e.newGroupId); - // Storing the expanded state of children of the moved group so that they can be expanded after the move. - const profileExpandedState = this._tree.getExpandedState(movedGroup); - oldParent.children = oldParent.children.filter(c => c.id !== movedGroup.id); - await this._tree.updateChildren(oldParent); - newParent.children.push(movedGroup); - (movedGroup).parent = newParent; - (movedGroup).parentId = newParent.id; - await this._tree.updateChildren(newParent); - await this._tree.revealSelectFocusElement(movedGroup); - // Expanding the previously expanded children of the moved group after the move. - this._tree.expandElements(profileExpandedState); - } - })); - return new Promise(async (resolve, reject) => { await this.refreshTree(); const root = this._tree!.getInput(); @@ -377,10 +216,9 @@ export class ServerTreeView extends Disposable implements IServerTreeView { const expandGroups = this._configurationService.getValue<{ autoExpand: boolean }>(SERVER_GROUP_CONFIG).autoExpand; if (expandGroups) { if (this._tree instanceof AsyncServerTree) { - const subGroups = ConnectionProfileGroup.getSubgroups(root); - for (let group of subGroups) { - await this._tree.expand(group); - } + await Promise.all(ConnectionProfileGroup.getSubgroups(root).map(subgroup => { + return this._tree!.expand(subgroup); + })); } else { await this._tree!.expandAll(ConnectionProfileGroup.getSubgroups(root)); } @@ -408,6 +246,26 @@ export class ServerTreeView extends Disposable implements IServerTreeView { } if (this._tree instanceof AsyncServerTree) { + // When new connection groups are added the event is fired with undefined so + // we still want to refresh the tree in that case to pick up the changes + await this.refreshTree(); + if (newProfile) { + const currentSelections = this._tree.getSelection(); + const currentSelectedElement = currentSelections && currentSelections.length >= 1 ? currentSelections[0] : undefined; + const newProfileIsSelected = currentSelectedElement && currentSelectedElement.id === newProfile.id; + // Clear any other selected elements first + if (currentSelectedElement && !newProfileIsSelected) { + this._tree.setSelection([]); + } + const newConnectionProfile = this.getConnectionInTreeInput(newProfile.id); + if (newConnectionProfile) { + // Re-render to update the connection status badge + this._tree.rerender(newConnectionProfile); + this._tree.setSelection([newConnectionProfile]); + this._tree.expand(newConnectionProfile); + } + } + } else { if (newProfile) { const groups = this._connectionManagementService.getConnectionGroups(); @@ -466,39 +324,17 @@ export class ServerTreeView extends Disposable implements IServerTreeView { } } - private async disconnectConnection(profile: ConnectionProfile, deleteConnFromConnectionService: boolean = false): Promise { - if (this._tree instanceof AsyncServerTree) { - if (deleteConnFromConnectionService) { - await this._connectionManagementService.deleteConnection(profile); - } - const connectionProfile = this.getConnectionInTreeInput(profile.id); - // Delete the node from the tree - await this._objectExplorerService.deleteObjectExplorerNode(connectionProfile); - // Collapse the node - await this._tree.collapse(connectionProfile); - // Rerendering node to turn the badge red - await this._tree.rerender(connectionProfile); - connectionProfile.isDisconnecting = true; - await this._tree.updateChildren(connectionProfile); - connectionProfile.isDisconnecting = false; - // Make the connection dirty so that the next expansion will refresh the node - await this._tree.makeElementDirty(connectionProfile); - await this._tree.revealSelectFocusElement(connectionProfile); - } - } - private async onObjectExplorerSessionCreated(connection: IConnectionProfile): Promise { const element = this.getConnectionInTreeInput(connection.id); if (element) { if (this._tree instanceof AsyncServerTree) { - await this._tree.rerender(element); - await this._tree.revealSelectFocusElement(element); + this._tree.rerender(element); } else { await this._tree!.refresh(element); - await this._tree!.expand(element); - await this._tree!.reveal(element, 0.5); - this._treeSelectionHandler.onTreeActionStateChange(false); } + await this._tree!.expand(element); + await this._tree!.reveal(element, 0.5); + this._treeSelectionHandler.onTreeActionStateChange(false); } } @@ -518,8 +354,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView { // Collapse the node before refreshing so the refresh doesn't try to fetch // the children again (which causes it to try and connect) this._tree.collapse(conn); - this._tree.rerender(conn); - this._tree.makeElementDirty(conn); + await this.refreshTree(); } else { await this._tree!.collapse(conn); return this._tree!.refresh(conn); @@ -581,7 +416,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView { /** * Set tree elements based on the view (recent/active) */ - public async showFilteredTree(view: ServerTreeViewView): Promise { + public showFilteredTree(view: ServerTreeViewView): void { hide(this.messages!); this._viewKey.set(view); const root = TreeUpdateUtils.getTreeInput(this._connectionManagementService); @@ -595,29 +430,24 @@ export class ServerTreeView extends Disposable implements IServerTreeView { } else { treeInput = filteredResults[0]; } - - if (this._tree instanceof AsyncServerTree) { - await this._tree.setInput(treeInput!); - await this._tree.updateChildren(treeInput!); - return; - } - await this._tree.setInput(treeInput!); - if (isHidden(this.messages!)) { - this._tree.getFocus(); - if (this._tree instanceof AsyncServerTree) { - for (const subgroup of ConnectionProfileGroup.getSubgroups(treeInput)) { - await this._tree.expand(subgroup); + this._tree!.setInput(treeInput!).then(async () => { + if (isHidden(this.messages!)) { + this._tree!.getFocus(); + if (this._tree instanceof AsyncServerTree) { + await Promise.all(ConnectionProfileGroup.getSubgroups(treeInput!).map(subgroup => { + this._tree!.expand(subgroup); + })); + } else { + await this._tree!.expandAll(ConnectionProfileGroup.getSubgroups(treeInput!)); } } else { - await this._tree!.expandAll(ConnectionProfileGroup.getSubgroups(treeInput!)); + if (this._tree instanceof AsyncServerTree) { + this._tree.setFocus([]); + } else { + this._tree!.clearFocus(); + } } - } else { - if (this._tree instanceof AsyncServerTree) { - this._tree.setFocus([]); - } else { - this._tree!.clearFocus(); - } - } + }, errors.onUnexpectedError); } else { //no op } diff --git a/src/sql/workbench/services/connection/browser/connectionManagementService.ts b/src/sql/workbench/services/connection/browser/connectionManagementService.ts index 8f10fac736..d3e8245d68 100644 --- a/src/sql/workbench/services/connection/browser/connectionManagementService.ts +++ b/src/sql/workbench/services/connection/browser/connectionManagementService.ts @@ -8,7 +8,7 @@ import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; import { IConnectionManagementService, INewConnectionParams, ConnectionType, IConnectableInput, IConnectionCompletionOptions, IConnectionCallbacks, - IConnectionParams, IConnectionResult, RunQueryOnConnectionMode, ConnectionElementMovedParams, ConnectionProfileEditedParams + IConnectionParams, IConnectionResult, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement'; import { ConnectionStore } from 'sql/platform/connection/common/connectionStore'; import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo'; @@ -76,17 +76,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti private _connectionGlobalStatus = new ConnectionGlobalStatus(this._notificationService); private _uriToReconnectPromiseMap: { [uri: string]: Promise } = {}; - private _onConnectionProfileCreated = new Emitter(); - private _onConnectionProfileDeleted = new Emitter(); - private _onConnectionProfileEdited = new Emitter(); - private _onConnectionProfileMoved = new Emitter(); - private _onConnectionProfileConnected = new Emitter(); - private _onConnectionProfileDisconnected = new Emitter(); - private _onConnectionProfileGroupCreated = new Emitter(); - private _onConnectionProfileGroupDeleted = new Emitter(); - private _onConnectionProfileGroupEdited = new Emitter(); - private _onConnectionProfileGroupMoved = new Emitter(); - private _mementoContext: Memento; private _mementoObj: MementoObject; private _connectionStore: ConnectionStore; @@ -202,49 +191,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti return this._onLanguageFlavorChanged.event; } - /** - * Async tree event emitters - */ - public get onConnectionProfileCreated(): Event { - return this._onConnectionProfileCreated.event; - } - - public get onConnectionProfileEdited(): Event { - return this._onConnectionProfileEdited.event; - } - - public get onConnectionProfileDeleted(): Event { - return this._onConnectionProfileDeleted.event; - } - - public get onConnectionProfileMoved(): Event { - return this._onConnectionProfileMoved.event; - } - - public get onConnectionProfileConnected(): Event { - return this._onConnectionProfileConnected.event; - } - - public get onConnectionProfileDisconnected(): Event { - return this._onConnectionProfileDisconnected.event; - } - - public get onConnectionProfileGroupCreated(): Event { - return this._onConnectionProfileGroupCreated.event; - } - - public get onConnectionProfileGroupDeleted(): Event { - return this._onConnectionProfileGroupDeleted.event; - } - - public get onConnectionProfileGroupEdited(): Event { - return this._onConnectionProfileGroupEdited.event; - } - - public get onConnectionProfileGroupMoved(): Event { - return this._onConnectionProfileGroupMoved.event; - } - public get providerNameToDisplayNameMap(): { readonly [providerDisplayName: string]: string } { return this._providerNameToDisplayNameMap; } @@ -593,18 +539,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti await this.saveToSettings(uri, connection, matcher).then(value => { this._onAddConnectionProfile.fire(connection); - if (isEdit) { - this._onConnectionProfileEdited.fire({ - oldProfileId: options.params.oldProfileId, - profile: connection - }); - } else { - if (options.params === undefined) { - this._onConnectionProfileConnected.fire(connection); - } else { - this._onConnectionProfileCreated.fire(connection); - } - } this.doActionsAfterConnectionComplete(value, options); }); } else { @@ -782,22 +716,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti } public getConnectionGroups(providers?: string[]): ConnectionProfileGroup[] { - const groups = this._connectionStore.getConnectionProfileGroups(false, providers); - return groups; - } - - public getConnectionGroupById(id: string): ConnectionProfileGroup | undefined { - const groups = this.getConnectionGroups(); - for (let group of groups) { - if (group.id === id) { - return group; - } - const subgroup = ConnectionProfileGroup.getSubgroups(group).find(g => g.id === id); - if (subgroup) { - return subgroup; - } - } - return undefined; + return this._connectionStore.getConnectionProfileGroups(false, providers); } public getRecentConnections(providers?: string[]): ConnectionProfile[] { @@ -826,15 +745,10 @@ export class ConnectionManagementService extends Disposable implements IConnecti } } - public saveProfileGroup(group: IConnectionProfileGroup): Promise { + public saveProfileGroup(profile: IConnectionProfileGroup): Promise { this._telemetryService.sendActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.AddServerGroup); - return this._connectionStore.saveProfileGroup(group).then(groupId => { + return this._connectionStore.saveProfileGroup(profile).then(groupId => { this._onAddConnectionProfile.fire(undefined); - //Getting id for the new profile group - group.id = groupId; - const parentGroup = this.getConnectionGroupById(group.parentId); - this._onConnectionProfileGroupCreated.fire(ConnectionProfileGroup.createConnectionProfileGroup(group, parentGroup)); - return groupId; }); } @@ -1310,30 +1224,21 @@ export class ConnectionManagementService extends Disposable implements IConnecti public onIntelliSenseCacheComplete(handle: number, connectionUri: string): void { } - public async changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise { + public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise { this._telemetryService.sendActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.MoveServerConnection); - await this._connectionStore.changeGroupIdForConnectionGroup(source, target); - this._onConnectionProfileGroupMoved.fire({ - source: source, - oldGroupId: source.parentId, - newGroupId: target.id - }); + return this._connectionStore.changeGroupIdForConnectionGroup(source, target); } - public async changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise { - const oldProfileId = source.groupId; + public changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise { let id = Utils.generateUri(source); this._telemetryService.sendActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.MoveServerGroup); - await this._connectionStore.changeGroupIdForConnection(source, targetGroupId) - this._onAddConnectionProfile.fire(source); - if (id && targetGroupId) { - source.groupId = targetGroupId; - } - this.changeConnectionUri(Utils.generateUri(source), id); - this._onConnectionProfileMoved.fire({ - source: source, - oldGroupId: oldProfileId, - newGroupId: targetGroupId, + return this._connectionStore.changeGroupIdForConnection(source, targetGroupId).then(result => { + this._onAddConnectionProfile.fire(source); + if (id && targetGroupId) { + source.groupId = targetGroupId; + } + // change the connection uri with the new group so that connection does not appear as disconnected in OE. + this.changeConnectionUri(Utils.generateUri(source), id); }); } @@ -1603,7 +1508,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti public editGroup(group: ConnectionProfileGroup): Promise { return this._connectionStore.editGroup(group).then(groupId => { this._onAddConnectionProfile.fire(undefined); - this._onConnectionProfileGroupEdited.fire(group); }); } @@ -1611,7 +1515,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti * Deletes a connection from registered servers. * Disconnects a connection before removing from settings. */ - public async deleteConnection(connection: ConnectionProfile): Promise { + public deleteConnection(connection: ConnectionProfile): Promise { this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.DeleteConnection) .withAdditionalProperties({ provider: connection.providerName @@ -1624,7 +1528,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti // Remove profile from configuration return this._connectionStore.deleteConnectionFromConfiguration(connection).then(() => { this._onDeleteConnectionProfile.fire(); - this._onConnectionProfileDeleted.fire(connection); return true; }); @@ -1636,7 +1539,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti // Remove disconnected profile from settings return this._connectionStore.deleteConnectionFromConfiguration(connection).then(() => { this._onDeleteConnectionProfile.fire(); - this._onConnectionProfileDeleted.fire(connection); return true; }); } @@ -1665,7 +1567,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti // Remove profiles and groups from config return this._connectionStore.deleteGroupFromConfiguration(group).then(() => { this._onDeleteConnectionProfile.fire(); - this._onConnectionProfileGroupDeleted.fire(group); return true; }); }).catch(() => false); diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts index bdca7cc7f4..effdecb0cf 100644 --- a/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts +++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts @@ -4,187 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; -import { IListService, IWorkbenchAsyncDataTreeOptions, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { FuzzyScore } from 'vs/base/common/filters'; import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; -import { IAsyncDataTreeNode, IAsyncDataTreeUpdateChildrenOptions, IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IAsyncDataSource, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; export class AsyncServerTree extends WorkbenchAsyncDataTree { - - constructor( - user: string, - container: HTMLElement, - delegate: IListVirtualDelegate, - renderers: ITreeRenderer[], - dataSource: IAsyncDataSource, - options: IWorkbenchAsyncDataTreeOptions, - @IContextKeyService contextKeyService: IContextKeyService, - @IListService listService: IListService, - @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService, - @IAccessibilityService accessibilityService: IAccessibilityService, - ) { - super( - user, container, delegate, - renderers, dataSource, options, - contextKeyService, listService, - themeService, configurationService, keybindingService, accessibilityService); - - // Adding support for expand/collapse on enter/space - this.onKeyDown(e => { - const standardKeyboardEvent = new StandardKeyboardEvent(e); - if (standardKeyboardEvent.keyCode === KeyCode.Enter || standardKeyboardEvent.keyCode === KeyCode.Space) { - const selectedElement = this.getSelection()[0]; - if (selectedElement) { - if (this.isCollapsed(selectedElement)) { - this.expand(selectedElement); - } else { - this.collapse(selectedElement); - } - } - } - }) - } - - // Overriding the setInput method to dispose the original input when a new input is set override async setInput(input: ConnectionProfileGroup, viewState?: IAsyncDataTreeViewState): Promise { const originalInput = this.getInput(); await super.setInput(input, viewState); originalInput?.dispose(); } - - /** - * The original implementation of getDataNode compares refrences of the elements to find the node. - * This is not working for our case as we are creating new elements everytime we refresh the tree. - * This method overrides the original implementation to find the node by comparing the ids of the elements. - * If the node is not found in the original implementation, we search for the node in the nodes map by ids. - */ - public override getDataNode(element: ConnectionProfileGroup | ServerTreeElement): IAsyncDataTreeNode { - try { - const node = super.getDataNode(element); - return node; - } catch (e) { - let node = this.getDataNodeById(element?.id); - if (node) { - return node; - } - throw e; - } - } - - /** - * Gets the element by id in the tree - */ - public getElementById(id: string): ServerTreeElement | undefined { - if (this.getInput().id === id) { - return this.getInput(); - } - return this.getDataNodeById(id)?.element; - } - - /** - * Get the list of expanded elements in the tree - */ - public getExpandedState(element: ServerTreeElement): ServerTreeElement[] { - const node = this.getDataNode(element); - const stack = [node]; - const expanded: ServerTreeElement[] = []; - while (stack.length > 0) { - const node = stack.pop(); - if (node) { - if (!this.isCollapsed(node.element)) { - expanded.push(node.element); - if (node.children) { - node.children.forEach(child => stack.push(child)); - } - } - } - } - return expanded; - } - - private getDataNodeById(id: string): IAsyncDataTreeNode | undefined { - let node = undefined; - this.nodes.forEach((v, k) => { - if (id === v?.id) { - node = v; - } - }); - return node; - } - - public override async updateChildren(element?: ServerTreeElement, recursive?: boolean, rerender?: boolean, options?: IAsyncDataTreeUpdateChildrenOptions): Promise { - const expandedChildren = this.getExpandedState(element); - await super.updateChildren(element, recursive, rerender, options); - await this.expandElements(expandedChildren); - } - - public async expandElements(elements: ServerTreeElement[]): Promise { - for (let element of elements) { - const id = element.id; - const node = this.getDataNodeById(id); - if (node) { - await this.expand(node.element); - } else { - // If the node is not found in the nodes map, we search for the node by comparing the relative paths of the elements - if (element) { - const elementPath = this.getRelativePath(element); - for (let n of this.nodes.values()) { - if (this.getRelativePath(n.element) === elementPath) { - await this.expand(n.element); - break; - } - } - } - } - } - } - - /** - * Get the relative path of the element in the tree. For connection and group, the path is the id of the element. - * For other elements, the path is the node path of the element and the id of the connection they belong to. - */ - private getRelativePath(element: ServerTreeElement): string { - let path = ''; - if (element instanceof TreeNode) { - path = element.nodePath; - let parent = element.parent; - while (parent.parent) { - parent = parent.parent; - } - if (parent.connection) { - path = parent.connection.id + '/' + path; - } - } else if (element instanceof ConnectionProfile || element instanceof ConnectionProfileGroup) { - path = element.id; - } - return path; - } - - /** - * Mark the element as dirty so that it will be refreshed when it is expanded next time - * @param element The element to mark as dirty - */ - public async makeElementDirty(element: ServerTreeElement) { - this.getDataNode(element).stale = true; - } - - public async revealSelectFocusElement(element: ServerTreeElement) { - await this.reveal(element); - await this.setSelection([element]); - this.setFocus([element]); - } } export type ServerTreeElement = ConnectionProfile | ConnectionProfileGroup | TreeNode; diff --git a/src/sql/workbench/services/objectExplorer/browser/dragAndDropController.ts b/src/sql/workbench/services/objectExplorer/browser/dragAndDropController.ts index 6f5340d7a4..c2393eb828 100644 --- a/src/sql/workbench/services/objectExplorer/browser/dragAndDropController.ts +++ b/src/sql/workbench/services/objectExplorer/browser/dragAndDropController.ts @@ -167,6 +167,7 @@ export class ServerTreeDragAndDrop implements IDragAndDrop { // to avoid creating a circular structure. canDragOver = source.id !== targetConnectionProfileGroup.id && !source.isAncestorOf(targetConnectionProfileGroup); } + return canDragOver; } @@ -178,41 +179,33 @@ export class ServerTreeDragAndDrop implements IDragAndDrop { public onDragOver(tree: AsyncServerTree | ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): IDragOverReaction { let canDragOver: boolean = true; - const source = data.getData()[0]; - if (source instanceof ConnectionProfileGroup) { - if (targetElement instanceof ConnectionProfileGroup) { - // If target group is parent of the source connection, then don't allow drag over - canDragOver = this.canDragToConnectionProfileGroup(source, targetElement); - } else if (targetElement instanceof ConnectionProfile) { - canDragOver = source.parentId !== targetElement.groupId; - } else if (targetElement instanceof TreeNode) { - const treeNodeParentGroupId = this.getTreeNodeParentGroup(targetElement).id; - canDragOver = source.parentId !== treeNodeParentGroupId && source.id !== treeNodeParentGroupId; + if (targetElement instanceof ConnectionProfile || targetElement instanceof ConnectionProfileGroup) { + let targetConnectionProfileGroup = this.getTargetGroup(targetElement); + // Verify if the connection can be moved to the target group + const source = data.getData()[0]; + if (source instanceof ConnectionProfile) { + if (!this._connectionManagementService.canChangeConnectionConfig(source, targetConnectionProfileGroup.id!)) { + canDragOver = false; + } + } else if (source instanceof ConnectionProfileGroup) { + // Dropping a group to itself or its descendants nodes is not allowed + // to avoid creating a circular structure. + canDragOver = source.id !== targetElement.id && !source.isAncestorOf(targetElement); } - } else if (source instanceof ConnectionProfile) { - if (targetElement instanceof ConnectionProfileGroup) { - canDragOver = this.canDragToConnectionProfileGroup(source, targetElement); - } else if (targetElement instanceof ConnectionProfile) { - canDragOver = source.groupId !== targetElement.groupId && - this._connectionManagementService.canChangeConnectionConfig(source, targetElement.groupId); - } else if (targetElement instanceof TreeNode) { - canDragOver = source.groupId !== this.getTreeNodeParentGroup(targetElement).id; - } - } else if (source instanceof TreeNode) { - canDragOver = false; + } else { + canDragOver = true; } + if (canDragOver) { if (targetElement instanceof ConnectionProfile) { const isConnected = this._connectionManagementService.isProfileConnected(targetElement); // Don't auto-expand disconnected connections - doing so will try to connect the connection // when expanded which is not something we want to support currently return DRAG_OVER_ACCEPT_BUBBLE_DOWN(isConnected); - } else if (targetElement instanceof ConnectionProfileGroup) { - return DRAG_OVER_ACCEPT_BUBBLE_DOWN(true); - } else { - // Don't auto-expand treeNodes as we don't support drag and drop on them - return DRAG_OVER_ACCEPT_BUBBLE_DOWN(false); } + // Auto-expand other elements (groups, tree nodes) so their children can be + // exposed for further dragging + return DRAG_OVER_ACCEPT_BUBBLE_DOWN(true); } else { return DRAG_OVER_REJECT; } @@ -231,32 +224,23 @@ export class ServerTreeDragAndDrop implements IDragAndDrop { let oldParent: ConnectionProfileGroup = source.getParent(); const self = this; if (this.isDropAllowed(targetConnectionProfileGroup, oldParent, source)) { - if (tree instanceof AsyncServerTree) { - if (oldParent && source && targetConnectionProfileGroup) { - if (source instanceof ConnectionProfileGroup) { - this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup); - } else if (source instanceof ConnectionProfile) { - this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id!); + + if (source instanceof ConnectionProfile) { + // Change group id of profile + this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id!).then(() => { + if (tree) { + TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup); } - } - } else { - if (source instanceof ConnectionProfile) { - // Change group id of profile - this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id!).then(async () => { - if (tree) { - TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup); - } + }); + } else if (source instanceof ConnectionProfileGroup) { + // Change parent id of group + this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(() => { + if (tree) { + TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService); + } - }); - } else if (source instanceof ConnectionProfileGroup) { - // Change parent id of group - this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(async () => { - if (tree) { - TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService); - } - }); - } + }); } } } @@ -266,17 +250,13 @@ export class ServerTreeDragAndDrop implements IDragAndDrop { TreeUpdateUtils.isInDragAndDrop = false; } - private getTargetGroup(targetElement: ConnectionProfileGroup | ConnectionProfile | TreeNode): ConnectionProfileGroup { + private getTargetGroup(targetElement: ConnectionProfileGroup | ConnectionProfile): ConnectionProfileGroup { let targetConnectionProfileGroup: ConnectionProfileGroup; if (targetElement instanceof ConnectionProfile) { targetConnectionProfileGroup = targetElement.getParent()!; - } else if (targetElement instanceof ConnectionProfileGroup) { + } + else { targetConnectionProfileGroup = targetElement; - } else if (targetElement instanceof TreeNode) { - targetConnectionProfileGroup = this.getTreeNodeParentGroup(targetElement); - if (!targetConnectionProfileGroup) { - throw new Error('Cannot find parent for the node'); - } } return targetConnectionProfileGroup; @@ -291,20 +271,6 @@ export class ServerTreeDragAndDrop implements IDragAndDrop { let isUnsavedDrag = source && (source instanceof ConnectionProfileGroup) && (source.id === UNSAVED_GROUP_ID); return (!isDropToSameLevel && !isDropToItself && !isUnsavedDrag); } - - private getTreeNodeParentGroup(element: TreeNode): ConnectionProfileGroup | undefined { - let treeNode = element; - while (!treeNode?.connection) { - treeNode = treeNode.parent; - } - if (treeNode) { - const groupId = treeNode.connection.groupId; - if (groupId) { - return this._connectionManagementService.getConnectionGroupById(groupId); - } - } - return undefined; - } } /** diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index b0d35a1482..833eead632 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -22,7 +22,7 @@ import { ScrollEvent } from 'vs/base/common/scrollable'; import { IThemable } from 'vs/base/common/styler'; import { isIterable } from 'vs/base/common/types'; -export interface IAsyncDataTreeNode { // {{SQL CARBON EDIT}} - exporting interface +interface IAsyncDataTreeNode { element: TInput | T; readonly parent: IAsyncDataTreeNode | null; readonly children: IAsyncDataTreeNode[]; @@ -311,7 +311,7 @@ export class AsyncDataTree implements IDisposable protected readonly tree: ObjectTree, TFilterData>; protected readonly root: IAsyncDataTreeNode; - protected readonly nodes = new Map>(); // {{SQL CARBON EDIT}}} making protected to access in subclass + private readonly nodes = new Map>(); private readonly sorter?: ITreeSorter; private readonly collapseByDefault?: { (e: T): boolean }; @@ -706,7 +706,7 @@ export class AsyncDataTree implements IDisposable // Implementation - protected getDataNode(element: TInput | T): IAsyncDataTreeNode { // {{SQL CARBON EDIT}} making protected to override + private getDataNode(element: TInput | T): IAsyncDataTreeNode { const node: IAsyncDataTreeNode | undefined = this.nodes.get((element === this.root.element ? null : element) as T); if (!node) {