diff --git a/src/sql/base/browser/ui/panel/tab.component.ts b/src/sql/base/browser/ui/panel/tab.component.ts index d3e2c68200..470d504f1a 100644 --- a/src/sql/base/browser/ui/panel/tab.component.ts +++ b/src/sql/base/browser/ui/panel/tab.component.ts @@ -29,19 +29,22 @@ export class TabComponent implements OnDestroy { @Input() public identifier: string; @Input() private visibilityType: 'if' | 'visibility' = 'if'; private rendered = false; + private destroyed: boolean = false; constructor( @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef ) { } public set active(val: boolean) { - this._active = val; - if (this.active) { - this.rendered = true; - } - this._cd.detectChanges(); - if (this.active && this._child) { - this._child.layout(); + if (!this.destroyed) { + this._active = val; + if (this.active) { + this.rendered = true; + } + this._cd.detectChanges(); + if (this.active && this._child) { + this._child.layout(); + } } } @@ -50,6 +53,7 @@ export class TabComponent implements OnDestroy { } ngOnDestroy() { + this.destroyed = true; if (this.actions && this.actions.length > 0) { this.actions.forEach((action) => action.dispose()); } diff --git a/src/sql/common/constants.ts b/src/sql/common/constants.ts index f0bf3d3e26..96428e4b07 100644 --- a/src/sql/common/constants.ts +++ b/src/sql/common/constants.ts @@ -13,3 +13,6 @@ export const SerializationDisabled = 'Saving results into different format disab */ export const RestoreFeatureName = 'restore'; export const BackupFeatureName = 'backup'; + +export const MssqlProviderId = 'MSSQL'; + diff --git a/src/sql/parts/connection/common/connectionContextKey.ts b/src/sql/parts/connection/common/connectionContextKey.ts index 35930f00c6..2b4737af1f 100644 --- a/src/sql/parts/connection/common/connectionContextKey.ts +++ b/src/sql/parts/connection/common/connectionContextKey.ts @@ -8,7 +8,7 @@ import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IConnectionProfile } from 'sqlops'; -export class ConnectionContextkey implements IContextKey { +export class ConnectionContextKey implements IContextKey { static Provider = new RawContextKey('connectionProvider', undefined); static Server = new RawContextKey('serverName', undefined); @@ -23,10 +23,10 @@ export class ConnectionContextkey implements IContextKey { constructor( @IContextKeyService contextKeyService: IContextKeyService ) { - this._providerKey = ConnectionContextkey.Provider.bindTo(contextKeyService); - this._serverKey = ConnectionContextkey.Server.bindTo(contextKeyService); - this._databaseKey = ConnectionContextkey.Database.bindTo(contextKeyService); - this._connectionKey = ConnectionContextkey.Connection.bindTo(contextKeyService); + this._providerKey = ConnectionContextKey.Provider.bindTo(contextKeyService); + this._serverKey = ConnectionContextKey.Server.bindTo(contextKeyService); + this._databaseKey = ConnectionContextKey.Database.bindTo(contextKeyService); + this._connectionKey = ConnectionContextKey.Connection.bindTo(contextKeyService); } set(value: IConnectionProfile) { diff --git a/src/sql/parts/connection/common/connectionManagement.ts b/src/sql/parts/connection/common/connectionManagement.ts index bc31d01c02..53e774a2f8 100644 --- a/src/sql/parts/connection/common/connectionManagement.ts +++ b/src/sql/parts/connection/common/connectionManagement.ts @@ -174,7 +174,7 @@ export interface IConnectionManagementService { disconnectEditor(owner: IConnectableInput, force?: boolean): Promise; - disconnect(connection: ConnectionProfile): Promise; + disconnect(connection: IConnectionProfile): Promise; disconnect(ownerUri: string): Promise; @@ -208,7 +208,7 @@ export interface IConnectionManagementService { */ cancelEditorConnection(owner: IConnectableInput): Thenable; - showDashboard(connection: ConnectionProfile): Thenable; + showDashboard(connection: IConnectionProfile): Thenable; closeDashboard(uri: string): void; @@ -216,7 +216,7 @@ export interface IConnectionManagementService { hasRegisteredServers(): boolean; - canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean; + canChangeConnectionConfig(profile: IConnectionProfile, newGroupID: string): boolean; getTabColorForUri(uri: string): string; diff --git a/src/sql/parts/connection/common/connectionManagementService.ts b/src/sql/parts/connection/common/connectionManagementService.ts index 93ab6259d1..b89bf37390 100644 --- a/src/sql/parts/connection/common/connectionManagementService.ts +++ b/src/sql/parts/connection/common/connectionManagementService.ts @@ -553,7 +553,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti }); } - public showDashboard(connection: ConnectionProfile): Thenable { + public showDashboard(connection: IConnectionProfile): Thenable { return this.showDashboardForConnectionManagementInfo(connection); } diff --git a/src/sql/parts/connection/common/connectionStore.ts b/src/sql/parts/connection/common/connectionStore.ts index a8afeabc22..c4333fc1b6 100644 --- a/src/sql/parts/connection/common/connectionStore.ts +++ b/src/sql/parts/connection/common/connectionStore.ts @@ -240,7 +240,7 @@ export class ConnectionStore { return connectionProfile; } else { return undefined; - }; + } }); } @@ -352,7 +352,7 @@ export class ConnectionStore { list.unshift(savedProfile); let newList = list.map(c => { - let connectionProfile = c ? c.toIConnectionProfile() : undefined;; + let connectionProfile = c ? c.toIConnectionProfile() : undefined; return connectionProfile; }); return newList.filter(n => n !== undefined); @@ -372,7 +372,7 @@ export class ConnectionStore { }); let newList = list.map(c => { - let connectionProfile = c ? c.toIConnectionProfile() : undefined;; + let connectionProfile = c ? c.toIConnectionProfile() : undefined; return connectionProfile; }); return newList.filter(n => n !== undefined); diff --git a/src/sql/parts/dashboard/dashboardEditor.ts b/src/sql/parts/dashboard/dashboardEditor.ts index 8d4356f149..97e9ba7534 100644 --- a/src/sql/parts/dashboard/dashboardEditor.ts +++ b/src/sql/parts/dashboard/dashboardEditor.ts @@ -18,7 +18,7 @@ import { DashboardModule } from './dashboard.module'; import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService'; import { IDashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams'; import { DASHBOARD_SELECTOR } from 'sql/parts/dashboard/dashboard.component'; -import { ConnectionContextkey } from 'sql/parts/connection/common/connectionContextKey'; +import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey'; import { IDashboardService } from 'sql/services/dashboard/common/dashboardService'; import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; @@ -110,7 +110,7 @@ export class DashboardEditor extends BaseEditor { let serverInfo = this._connMan.getConnectionInfo(this.input.uri).serverInfo; this._dashboardService.changeToDashboard({ profile, serverInfo }); let scopedContextService = this._contextKeyService.createScoped(input.container); - let connectionContextKey = new ConnectionContextkey(scopedContextService); + let connectionContextKey = new ConnectionContextKey(scopedContextService); connectionContextKey.set(input.connectionProfile); let params: IDashboardComponentParams = { diff --git a/src/sql/parts/objectExplorer/common/objectExplorerService.ts b/src/sql/parts/objectExplorer/common/objectExplorerService.ts index a4ac817ca1..1874060ebb 100644 --- a/src/sql/parts/objectExplorer/common/objectExplorerService.ts +++ b/src/sql/parts/objectExplorer/common/objectExplorerService.ts @@ -162,10 +162,16 @@ export class ObjectExplorerService implements IObjectExplorerService { error(expandResponse.errorMessage); } - let nodeStatus = this._sessions[expandResponse.sessionId].nodes[expandResponse.nodePath]; - if (nodeStatus && nodeStatus.expandEmitter) { - nodeStatus.expandEmitter.fire(expandResponse); - } else { + let sessionStatus = this._sessions[expandResponse.sessionId]; + let foundSession = false; + if (sessionStatus) { + let nodeStatus = this._sessions[expandResponse.sessionId].nodes[expandResponse.nodePath]; + foundSession = !!nodeStatus; + if (foundSession && nodeStatus.expandEmitter) { + nodeStatus.expandEmitter.fire(expandResponse); + } + } + if (!foundSession) { warn(`Cannot find node status for session: ${expandResponse.sessionId} and node path: ${expandResponse.nodePath}`); } } diff --git a/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts b/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts index 6f43a865aa..dcfffb47cc 100644 --- a/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts +++ b/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts @@ -33,7 +33,7 @@ export class RefreshAction extends Action { id: string, label: string, tree: ITree, - private element: ConnectionProfile | TreeNode, + private element: IConnectionProfile | TreeNode, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IErrorMessageService private _errorMessageService: IErrorMessageService @@ -88,80 +88,31 @@ export class DisconnectConnectionAction extends Action { public static ID = 'objectExplorer.disconnect'; public static LABEL = localize('DisconnectAction', 'Disconnect'); - private _disposables: IDisposable[] = []; - private _connectionProfile: ConnectionProfile; - - private _container: HTMLElement; - constructor( id: string, label: string, + private _connectionProfile: ConnectionProfile, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IErrorMessageService private _errorMessageService: IErrorMessageService ) { super(id, label); - const self = this; - this._disposables.push(this._connectionManagementService.onConnect(() => { - self.setLabel(); - }) - ); - this._disposables.push(this._connectionManagementService.onDisconnect((disconnectParams) => { - if (this._connectionProfile) { - this._connectionProfile.isDisconnecting = false; - } - self.setLabel(); - self._connectionManagementService.closeDashboard(disconnectParams.connectionUri); - }) - ); - if (this._objectExplorerService && this._objectExplorerService.onUpdateObjectExplorerNodes) { - this._disposables.push(this._objectExplorerService.onUpdateObjectExplorerNodes((args) => { - self.removeSpinning(args.connection); - if (args.errorMessage !== undefined) { - self.showError(args.errorMessage); - } - }) - ); - } - } - - private showError(errorMessage: string) { - if (this._errorMessageService) { - this._errorMessageService.showDialog(Severity.Error, '', errorMessage); - } - } - - private setLabel(): void { - if (!this._connectionProfile) { - this.label = 'Connect'; - return; - } - this.label = this._connectionManagementService.isProfileConnected(this._connectionProfile) ? 'Disconnect' : 'Connect'; - } - - private removeSpinning(connection: IConnectionProfile): void { - if (this._connectionProfile) { - if (connection.id === this._connectionProfile.id && this._container) { - ObjectExplorerActionUtilities.hideLoadingIcon(this._container, ObjectExplorerActionUtilities.connectionElementClass); - } - } } run(actionContext: ObjectExplorerActionsContext): TPromise { return new TPromise((resolve, reject) => { - if (actionContext instanceof ObjectExplorerActionsContext) { - //set objectExplorerTreeNode for context menu clicks - this._connectionProfile = actionContext.connectionProfile; - this._container = actionContext.container; - resolve(true); - } - if (!this._connectionProfile) { resolve(true); } if (this._connectionManagementService.isProfileConnected(this._connectionProfile)) { - this._connectionProfile.isDisconnecting = true; + let profileImpl = this._connectionProfile as ConnectionProfile; + if (profileImpl) { + profileImpl.isDisconnecting = true; + } this._connectionManagementService.disconnect(this._connectionProfile).then((value) => { + if (profileImpl) { + profileImpl.isDisconnecting = false; + } resolve(true); } ).catch(disconnectError => { @@ -172,11 +123,6 @@ export class DisconnectConnectionAction extends Action { } }); } - - dispose(): void { - super.dispose(); - this._disposables = dispose(this._disposables); - } } @@ -362,11 +308,11 @@ export class RecentConnectionsFilterAction extends Action { export class NewQueryAction extends Action { public static ID = 'registeredServers.newQuery'; public static LABEL = localize('registeredServers.newQuery', 'New Query'); - private _connectionProfile: ConnectionProfile; - get connectionProfile(): ConnectionProfile { + private _connectionProfile: IConnectionProfile; + get connectionProfile(): IConnectionProfile { return this._connectionProfile; } - set connectionProfile(profile: ConnectionProfile) { + set connectionProfile(profile: IConnectionProfile) { this._connectionProfile = profile; } @@ -403,7 +349,7 @@ export class DeleteConnectionAction extends Action { constructor( id: string, label: string, - private element: ConnectionProfile | ConnectionProfileGroup, + private element: IConnectionProfile | ConnectionProfileGroup, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService ) { super(id, label); @@ -413,7 +359,6 @@ export class DeleteConnectionAction extends Action { } if (element instanceof ConnectionProfile) { - element = element; let parent: ConnectionProfileGroup = element.parent; if (parent && parent.id === Constants.unsavedGroupId) { this.enabled = false; diff --git a/src/sql/parts/objectExplorer/viewlet/objectExplorerActions.ts b/src/sql/parts/objectExplorer/viewlet/objectExplorerActions.ts index 65fcaa2c31..dbfcf883f5 100644 --- a/src/sql/parts/objectExplorer/viewlet/objectExplorerActions.ts +++ b/src/sql/parts/objectExplorer/viewlet/objectExplorerActions.ts @@ -8,11 +8,14 @@ import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { Action } from 'vs/base/common/actions'; import { ITree } from 'vs/base/parts/tree/browser/tree'; +import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; + +import * as sqlops from 'sqlops'; import { IConnectionManagementService, IConnectionCompletionOptions, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement'; import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; -import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; import { - NewQueryAction, ScriptSelectAction, EditDataAction, ScriptCreateAction, + ScriptSelectAction, EditDataAction, ScriptCreateAction, ScriptExecuteAction, ScriptDeleteAction, ScriptAlterAction } from 'sql/workbench/common/actions'; import { NodeType } from 'sql/parts/objectExplorer/common/nodeType'; @@ -23,41 +26,51 @@ import { IScriptingService } from 'sql/services/scripting/scriptingService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService'; import * as Constants from 'sql/parts/connection/common/constants'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; +import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; +import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; -export class ObjectExplorerActionsContext { - public treeNode: TreeNode; - public connectionProfile: ConnectionProfile; - public container: HTMLElement; - public tree: ITree; + +export class ObjectExplorerActionsContext implements sqlops.ObjectExplorerContext { + public connectionProfile: IConnectionProfile; + public nodeInfo: sqlops.NodeInfo; + public isConnectionNode: boolean = false; } +async function getTreeNode(context: ObjectExplorerActionsContext, objectExplorerService: IObjectExplorerService): TPromise { + if (context.isConnectionNode) { + return Promise.resolve(undefined); + } + return await objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath); +} + + export class OEAction extends ExecuteCommandAction { - private _objectExplorerTreeNode: TreeNode; - private _container: HTMLElement; private _treeSelectionHandler: TreeSelectionHandler; constructor( id: string, label: string, @IInstantiationService private _instantiationService: IInstantiationService, @ICommandService commandService: ICommandService, - @IConnectionManagementService private _connectionManagementService: IConnectionManagementService + @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, + @IObjectExplorerService private _objectExplorerService: IObjectExplorerService ) { super(id, label, commandService); } - public run(actionContext: any): TPromise { + public async run(actionContext: any): TPromise { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); let profile: IConnectionProfile; - if (actionContext.connectionProfile) { - profile = actionContext.connectionProfile; - } else { - profile = TreeUpdateUtils.getConnectionProfile(actionContext.treeNode); + if (actionContext instanceof ObjectExplorerActionsContext) { + if (actionContext.isConnectionNode) { + profile = actionContext.connectionProfile; + } else { + // Get the "correct" version from the tree + let treeNode = await getTreeNode(actionContext, this._objectExplorerService); + profile = TreeUpdateUtils.getConnectionProfile(treeNode); + } } this._treeSelectionHandler.onTreeActionStateChange(true); @@ -72,18 +85,16 @@ export class ManageConnectionAction extends Action { public static ID = 'objectExplorer.manage'; public static LABEL = localize('ManageAction', 'Manage'); - private _connectionProfile: ConnectionProfile; - private _objectExplorerTreeNode: TreeNode; private _treeSelectionHandler: TreeSelectionHandler; - protected _container: HTMLElement; - constructor( id: string, label: string, + private _tree: ITree, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, + @ICapabilitiesService protected _capabilitiesService: ICapabilitiesService, @IInstantiationService private _instantiationService: IInstantiationService, - @IObjectExplorerService private _objectExplorerService?: IObjectExplorerService, + @IObjectExplorerService private _objectExplorerService: IObjectExplorerService ) { super(id, label); } @@ -91,33 +102,37 @@ export class ManageConnectionAction extends Action { run(actionContext: ObjectExplorerActionsContext): TPromise { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler.onTreeActionStateChange(true); + let self = this; let promise = new TPromise((resolve, reject) => { - this.doManage(actionContext).then((success) => { - this.done(); + self.doManage(actionContext).then((success) => { + self.done(); resolve(success); }, error => { - this.done(); + self.done(); reject(error); }); }); return promise; } - private doManage(actionContext: ObjectExplorerActionsContext): Thenable { + private async doManage(actionContext: ObjectExplorerActionsContext): TPromise { + let treeNode: TreeNode = undefined; + let connectionProfile: IConnectionProfile = undefined; if (actionContext instanceof ObjectExplorerActionsContext) { - //set objectExplorerTreeNode for context menu clicks - this._connectionProfile = actionContext.connectionProfile; - this._objectExplorerTreeNode = actionContext.treeNode; - if (this._connectionProfile === undefined && TreeUpdateUtils.isDatabaseNode(this._objectExplorerTreeNode)) { - this._connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode); + // Must use a real connection profile for this action due to lookup + connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, actionContext.connectionProfile); + if (!actionContext.isConnectionNode) { + treeNode = await getTreeNode(actionContext, this._objectExplorerService); + if (TreeUpdateUtils.isDatabaseNode(treeNode)) { + connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode); + } } - this._container = actionContext.container; } - if (!this._connectionProfile) { + if (!connectionProfile) { // This should never happen. There should be always a valid connection if the manage action is called for // an OE node or a database node - return TPromise.wrap(true); + return true; } let options: IConnectionCompletionOptions = { @@ -130,10 +145,10 @@ export class ManageConnectionAction extends Action { // If it's a database node just open a database connection and open dashboard, // the node is already from an open OE session we don't need to create new session - if (TreeUpdateUtils.isAvailableDatabaseNode(this._objectExplorerTreeNode)) { - return this._connectionManagementService.showDashboard(this._connectionProfile); + if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) { + return this._connectionManagementService.showDashboard(connectionProfile); } else { - return TreeUpdateUtils.connectAndCreateOeSession(this._connectionProfile, options, this._connectionManagementService, this._objectExplorerService, actionContext.tree); + return TreeUpdateUtils.connectAndCreateOeSession(connectionProfile, options, this._connectionManagementService, this._objectExplorerService, this._tree); } } @@ -149,7 +164,6 @@ export class ManageConnectionAction extends Action { export class OEScriptSelectAction extends ScriptSelectAction { public static ID = 'objectExplorer.' + ScriptSelectAction.ID; private _objectExplorerTreeNode: TreeNode; - private _container: HTMLElement; private _treeSelectionHandler: TreeSelectionHandler; constructor( @@ -157,23 +171,23 @@ export class OEScriptSelectAction extends ScriptSelectAction { @IQueryEditorService protected _queryEditorService: IQueryEditorService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IScriptingService protected _scriptingService: IScriptingService, + @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IInstantiationService private _instantiationService: IInstantiationService ) { super(id, label, _queryEditorService, _connectionManagementService, _scriptingService); } - public run(actionContext: any): TPromise { + public async run(actionContext: any): TPromise { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); if (actionContext instanceof ObjectExplorerActionsContext) { //set objectExplorerTreeNode for context menu clicks - this._objectExplorerTreeNode = actionContext.treeNode; - this._container = actionContext.container; + this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService); } this._treeSelectionHandler.onTreeActionStateChange(true); - var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode); + var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode); var ownerUri = this._connectionManagementService.getConnectionId(connectionProfile); ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile); - var metadata = (this._objectExplorerTreeNode).metadata; + var metadata = this._objectExplorerTreeNode.metadata; return super.run({ profile: connectionProfile, object: metadata }).then((result) => { this._treeSelectionHandler.onTreeActionStateChange(false); @@ -185,7 +199,6 @@ export class OEScriptSelectAction extends ScriptSelectAction { export class OEEditDataAction extends EditDataAction { public static ID = 'objectExplorer.' + EditDataAction.ID; private _objectExplorerTreeNode: TreeNode; - private _container: HTMLElement; private _treeSelectionHandler: TreeSelectionHandler; constructor( @@ -193,17 +206,17 @@ export class OEEditDataAction extends EditDataAction { @IQueryEditorService protected _queryEditorService: IQueryEditorService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IScriptingService protected _scriptingService: IScriptingService, + @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IInstantiationService private _instantiationService: IInstantiationService ) { super(id, label, _queryEditorService, _connectionManagementService, _scriptingService); } - public run(actionContext: any): TPromise { + public async run(actionContext: any): TPromise { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); if (actionContext instanceof ObjectExplorerActionsContext) { //set objectExplorerTreeNode for context menu clicks - this._objectExplorerTreeNode = actionContext.treeNode; - this._container = actionContext.container; + this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService); } this._treeSelectionHandler.onTreeActionStateChange(true); var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode); @@ -219,7 +232,6 @@ export class OEEditDataAction extends EditDataAction { export class OEScriptCreateAction extends ScriptCreateAction { public static ID = 'objectExplorer.' + ScriptCreateAction.ID; private _objectExplorerTreeNode: TreeNode; - private _container: HTMLElement; private _treeSelectionHandler: TreeSelectionHandler; constructor( @@ -227,18 +239,18 @@ export class OEScriptCreateAction extends ScriptCreateAction { @IQueryEditorService protected _queryEditorService: IQueryEditorService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IScriptingService protected _scriptingService: IScriptingService, + @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IInstantiationService private _instantiationService: IInstantiationService, @IErrorMessageService protected _errorMessageService: IErrorMessageService ) { super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService); } - public run(actionContext: any): TPromise { + public async run(actionContext: any): TPromise { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); if (actionContext instanceof ObjectExplorerActionsContext) { //set objectExplorerTreeNode for context menu clicks - this._objectExplorerTreeNode = actionContext.treeNode; - this._container = actionContext.container; + this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService); } this._treeSelectionHandler.onTreeActionStateChange(true); var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode); @@ -256,7 +268,6 @@ export class OEScriptCreateAction extends ScriptCreateAction { export class OEScriptExecuteAction extends ScriptExecuteAction { public static ID = 'objectExplorer.' + ScriptExecuteAction.ID; private _objectExplorerTreeNode: TreeNode; - private _container: HTMLElement; private _treeSelectionHandler: TreeSelectionHandler; constructor( @@ -264,18 +275,18 @@ export class OEScriptExecuteAction extends ScriptExecuteAction { @IQueryEditorService protected _queryEditorService: IQueryEditorService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IScriptingService protected _scriptingService: IScriptingService, + @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IInstantiationService private _instantiationService: IInstantiationService, @IErrorMessageService protected _errorMessageService: IErrorMessageService ) { super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService); } - public run(actionContext: any): TPromise { + public async run(actionContext: any): TPromise { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); if (actionContext instanceof ObjectExplorerActionsContext) { //set objectExplorerTreeNode for context menu clicks - this._objectExplorerTreeNode = actionContext.treeNode; - this._container = actionContext.container; + this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService); } this._treeSelectionHandler.onTreeActionStateChange(true); var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode); @@ -293,7 +304,6 @@ export class OEScriptExecuteAction extends ScriptExecuteAction { export class OEScriptAlterAction extends ScriptAlterAction { public static ID = 'objectExplorer.' + ScriptAlterAction.ID; private _objectExplorerTreeNode: TreeNode; - private _container: HTMLElement; private _treeSelectionHandler: TreeSelectionHandler; constructor( @@ -301,18 +311,18 @@ export class OEScriptAlterAction extends ScriptAlterAction { @IQueryEditorService protected _queryEditorService: IQueryEditorService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IScriptingService protected _scriptingService: IScriptingService, + @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IInstantiationService private _instantiationService: IInstantiationService, @IErrorMessageService protected _errorMessageService: IErrorMessageService ) { super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService); } - public run(actionContext: any): TPromise { + public async run(actionContext: any): TPromise { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); if (actionContext instanceof ObjectExplorerActionsContext) { //set objectExplorerTreeNode for context menu clicks - this._objectExplorerTreeNode = actionContext.treeNode; - this._container = actionContext.container; + this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService); } this._treeSelectionHandler.onTreeActionStateChange(true); var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode); @@ -330,7 +340,6 @@ export class OEScriptAlterAction extends ScriptAlterAction { export class OEScriptDeleteAction extends ScriptDeleteAction { public static ID = 'objectExplorer.' + ScriptDeleteAction.ID; private _objectExplorerTreeNode: TreeNode; - private _container: HTMLElement; private _treeSelectionHandler: TreeSelectionHandler; constructor( @@ -338,18 +347,18 @@ export class OEScriptDeleteAction extends ScriptDeleteAction { @IQueryEditorService protected _queryEditorService: IQueryEditorService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IScriptingService protected _scriptingService: IScriptingService, + @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IInstantiationService private _instantiationService: IInstantiationService, @IErrorMessageService protected _errorMessageService: IErrorMessageService ) { super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService); } - public run(actionContext: any): TPromise { + public async run(actionContext: any): TPromise { this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); if (actionContext instanceof ObjectExplorerActionsContext) { //set objectExplorerTreeNode for context menu clicks - this._objectExplorerTreeNode = actionContext.treeNode; - this._container = actionContext.container; + this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService); } this._treeSelectionHandler.onTreeActionStateChange(true); var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode); @@ -364,77 +373,11 @@ export class OEScriptDeleteAction extends ScriptDeleteAction { } } -export class DisconnectAction extends Action { - public static ID = 'objectExplorer.disconnect'; - public static LABEL = localize('objectExplorAction.disconnect', 'Disconnect'); - private _objectExplorerTreeNode: TreeNode; - private _container: HTMLElement; - private _treeSelectionHandler: TreeSelectionHandler; - - constructor( - id: string, - label: string, - @IConnectionManagementService private connectionManagementService: IConnectionManagementService, - @IInstantiationService private _instantiationService: IInstantiationService - ) { - super(id, label); - } - - public run(actionContext: any): TPromise { - this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); - if (actionContext instanceof ObjectExplorerActionsContext) { - //set objectExplorerTreeNode for context menu clicks - this._objectExplorerTreeNode = actionContext.treeNode; - this._container = actionContext.container; - } - - var connectionProfile = (this._objectExplorerTreeNode).getConnectionProfile(); - if (this.connectionManagementService.isProfileConnected(connectionProfile)) { - this._treeSelectionHandler.onTreeActionStateChange(true); - - this.connectionManagementService.disconnect(connectionProfile).then(() => { - this._treeSelectionHandler.onTreeActionStateChange(false); - }); - } - - return TPromise.as(true); - } -} - export class ObjectExplorerActionUtilities { public static readonly objectExplorerElementClass = 'object-element-group'; public static readonly connectionElementClass = 'connection-tile'; - - private static getGroupContainer(container: HTMLElement, elementName: string): HTMLElement { - var element = container; - while (element && element.className !== elementName) { - element = element.parentElement; - } - return element ? element.parentElement : undefined; - } - - public static showLoadingIcon(container: HTMLElement, elementName: string): void { - if (container) { - let groupContainer = this.getGroupContainer(container, elementName); - if (groupContainer) { - groupContainer.classList.add('icon'); - groupContainer.classList.add('in-progress'); - } - } - } - - public static hideLoadingIcon(container: HTMLElement, elementName: string): void { - if (container) { - let element = this.getGroupContainer(container, elementName); - if (element && element.classList) { - element.classList.remove('icon'); - element.classList.remove('in-progress'); - } - } - } - public static getScriptMap(treeNode: TreeNode): Map { let scriptMap = new Map(); diff --git a/src/sql/parts/objectExplorer/viewlet/serverTreeActionProvider.ts b/src/sql/parts/objectExplorer/viewlet/serverTreeActionProvider.ts index 8f9eff76a9..44098fe349 100644 --- a/src/sql/parts/objectExplorer/viewlet/serverTreeActionProvider.ts +++ b/src/sql/parts/objectExplorer/viewlet/serverTreeActionProvider.ts @@ -9,6 +9,8 @@ import { ITree } from 'vs/base/parts/tree/browser/tree'; import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { IAction } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { DisconnectConnectionAction, AddServerAction, @@ -16,9 +18,7 @@ import { } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction'; import { - DisconnectAction, ObjectExplorerActionUtilities, - ManageConnectionAction, - OEAction + ObjectExplorerActionUtilities, ManageConnectionAction, OEAction } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions'; import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; import { NodeType } from 'sql/parts/objectExplorer/common/nodeType'; @@ -27,8 +27,14 @@ import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile import { NewProfilerAction } from 'sql/parts/profiler/contrib/profilerActions'; import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils'; import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; -import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenuService } from 'vs/platform/actions/common/actions'; import { NewQueryAction } from 'sql/workbench/common/actions'; +import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { TreeNodeContextKey } from './treeNodeContextKey'; +import { IQueryManagementService } from 'sql/parts/query/common/queryManagement'; +import { IScriptingService } from 'sql/services/scripting/scriptingService'; +import * as constants from 'sql/common/constants'; /** * Provides actions for the server tree elements @@ -37,7 +43,12 @@ export class ServerTreeActionProvider extends ContributableActionProvider { constructor( @IInstantiationService private _instantiationService: IInstantiationService, - @IConnectionManagementService private _connectionManagementService: IConnectionManagementService + @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, + @IQueryManagementService private _queryManagementService: IQueryManagementService, + @IScriptingService private _scriptingService: IScriptingService, + @IContextMenuService private contextMenuService: IContextMenuService, + @IMenuService private menuService: IMenuService, + @IContextKeyService private _contextKeyService: IContextKeyService ) { super(); } @@ -57,8 +68,11 @@ export class ServerTreeActionProvider extends ContributableActionProvider { return TPromise.as(this.getConnectionProfileGroupActions(tree, element)); } if (element instanceof TreeNode) { - var treeNode = element; - return TPromise.as(this.getObjectExplorerNodeActions(tree, treeNode)); + return TPromise.as(this.getObjectExplorerNodeActions({ + tree: tree, + profile: element.getConnectionProfile(), + treeNode: element + })); } return TPromise.as([]); @@ -75,23 +89,58 @@ export class ServerTreeActionProvider extends ContributableActionProvider { /** * Return actions for connection elements */ - public getConnectionActions(tree: ITree, element: ConnectionProfile): IAction[] { - let actions: IAction[] = []; - actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL)); - actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL)); - if (this._connectionManagementService.isProfileConnected(element)) { - actions.push(this._instantiationService.createInstance(DisconnectConnectionAction, DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL)); - } - actions.push(this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, element)); - actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, tree, element)); + public getConnectionActions(tree: ITree, profile: ConnectionProfile): IAction[] { + return this.getAllActions({ + tree: tree, + profile: profile + }, (context) => this.getBuiltinConnectionActions(context)); + } - if (process.env['VSCODE_DEV']) { + private getAllActions(context: ObjectExplorerContext, getDefaultActions: (ObjectExplorerContext) => IAction[]) { + // Create metadata needed to get a useful set of actions + let scopedContextService = this.getContextKeyService(context); + let menu = this.menuService.createMenu(MenuId.ObjectExplorerItemContext, scopedContextService); + + // Fill in all actions + let actions = getDefaultActions(context); + fillInActions(menu, { arg: undefined, shouldForwardArgs: true }, actions, this.contextMenuService); + + // Cleanup + scopedContextService.dispose(); + menu.dispose(); + return actions; + + } + + private getBuiltinConnectionActions(context: ObjectExplorerContext): IAction[] { + let actions: IAction[] = []; + actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL, context.tree)); + this.addNewQueryAction(context, actions); + + if (this._connectionManagementService.isProfileConnected(context.profile)) { + actions.push(this._instantiationService.createInstance(DisconnectConnectionAction, DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL, context.profile)); + } + actions.push(this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, context.profile)); + actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, context.profile)); + + if (process.env['VSCODE_DEV'] && constants.MssqlProviderId === context.profile.providerName) { actions.push(this._instantiationService.createInstance(OEAction, NewProfilerAction.ID, NewProfilerAction.LABEL)); } return actions; } + private getContextKeyService(context: ObjectExplorerContext): IContextKeyService { + let scopedContextService = this._contextKeyService.createScoped(); + let connectionContextKey = new ConnectionContextKey(scopedContextService); + connectionContextKey.set(context.profile); + let treeNodeContextKey = new TreeNodeContextKey(scopedContextService); + if (context.treeNode) { + treeNodeContextKey.set(context.treeNode); + } + return scopedContextService; + } + /** * Return actions for connection group elements */ @@ -106,32 +155,50 @@ export class ServerTreeActionProvider extends ContributableActionProvider { /** * Return actions for OE elements */ - public getObjectExplorerNodeActions(tree: ITree, treeNode: TreeNode): IAction[] { - let actions = []; + private getObjectExplorerNodeActions(context: ObjectExplorerContext): IAction[] { + return this.getAllActions(context, (context) => this.getBuiltInNodeActions(context)); + } + + private getBuiltInNodeActions(context: ObjectExplorerContext): IAction[] { + let actions: IAction[] = []; + let treeNode = context.treeNode; if (TreeUpdateUtils.isDatabaseNode(treeNode)) { if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) { - actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL)); + actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL, context.tree)); } else { return actions; } } - actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL)); - let scriptMap: Map = ObjectExplorerActionUtilities.getScriptMap(treeNode); - let supportedActions = scriptMap.get(treeNode.nodeTypeId); - let self = this; - if (supportedActions !== null && supportedActions !== undefined) { - supportedActions.forEach(action => { - actions.push(self._instantiationService.createInstance(action, action.ID, action.LABEL)); - }); - } - actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, tree, treeNode)); - - - if (treeNode.isTopLevel()) { - actions.push(this._instantiationService.createInstance(DisconnectAction, DisconnectAction.ID, DisconnectAction.LABEL)); - } + this.addNewQueryAction(context, actions); + this.addScriptingActions(context, actions); + actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, treeNode)); return actions; } + + private addNewQueryAction(context: ObjectExplorerContext, actions: IAction[]): void { + if (this._queryManagementService.isProviderRegistered(context.profile.providerName)) { + actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL)); + } + } + + private addScriptingActions(context: ObjectExplorerContext, actions: IAction[]): void { + if (this._scriptingService.isProviderRegistered(context.profile.providerName)) { + let scriptMap: Map = ObjectExplorerActionUtilities.getScriptMap(context.treeNode); + let supportedActions = scriptMap.get(context.treeNode.nodeTypeId); + let self = this; + if (supportedActions !== null && supportedActions !== undefined) { + supportedActions.forEach(action => { + actions.push(self._instantiationService.createInstance(action, action.ID, action.LABEL)); + }); + } + } + } +} + +interface ObjectExplorerContext { + tree: ITree; + profile: ConnectionProfile; + treeNode?: TreeNode; } diff --git a/src/sql/parts/objectExplorer/viewlet/serverTreeController.ts b/src/sql/parts/objectExplorer/viewlet/serverTreeController.ts index 23c3b13e9c..5ba769d34d 100644 --- a/src/sql/parts/objectExplorer/viewlet/serverTreeController.ts +++ b/src/sql/parts/objectExplorer/viewlet/serverTreeController.ts @@ -73,26 +73,21 @@ export class ServerTreeController extends treedefaults.DefaultController { event.stopPropagation(); tree.setFocus(element); - let parent: ConnectionProfileGroup = undefined; - if (element instanceof ConnectionProfileGroup) { - parent = element; - } - else if (element instanceof ConnectionProfile) { - parent = (element).parent; - } var actionContext: any; if (element instanceof TreeNode) { - actionContext = new ObjectExplorerActionsContext(); - actionContext.container = event.target; - actionContext.treeNode = element; - actionContext.tree = tree; + let context = new ObjectExplorerActionsContext(); + context.nodeInfo = element.toNodeInfo(); + context.connectionProfile = element.getConnectionProfile().toIConnectionProfile(); + actionContext = context; } else if (element instanceof ConnectionProfile) { - actionContext = new ObjectExplorerActionsContext(); - actionContext.container = event.target; - actionContext.connectionProfile = element; - actionContext.tree = tree; + let context = new ObjectExplorerActionsContext(); + context.connectionProfile = element.toIConnectionProfile(); + context.isConnectionNode = true; + actionContext = context; } else { + // TODO: because the connection group is used as a context object and isn't serializable, + // the Group-level context menu is not currently extensible actionContext = element; } diff --git a/src/sql/parts/objectExplorer/viewlet/serverTreeDataSource.ts b/src/sql/parts/objectExplorer/viewlet/serverTreeDataSource.ts index 3036656b51..495c20c576 100644 --- a/src/sql/parts/objectExplorer/viewlet/serverTreeDataSource.ts +++ b/src/sql/parts/objectExplorer/viewlet/serverTreeDataSource.ts @@ -31,12 +31,10 @@ export class ServerTreeDataSource implements IDataSource { * No more than one element may use a given identifier. */ public getId(tree: ITree, element: any): string { - if (element instanceof ConnectionProfile) { - return (element).id; - } else if (element instanceof ConnectionProfileGroup) { - return (element).id; - } else if (element instanceof TreeNode) { - return (element).id; + if (element instanceof ConnectionProfile + || element instanceof ConnectionProfileGroup + || element instanceof TreeNode) { + return element.id; } else { return undefined; } @@ -49,9 +47,9 @@ export class ServerTreeDataSource implements IDataSource { if (element instanceof ConnectionProfile) { return true; } else if (element instanceof ConnectionProfileGroup) { - return (element).hasChildren(); + return element.hasChildren(); } else if (element instanceof TreeNode) { - return !(element).isAlwaysLeaf; + return !element.isAlwaysLeaf; } return false; } @@ -70,7 +68,7 @@ export class ServerTreeDataSource implements IDataSource { } else if (element instanceof ConnectionProfileGroup) { resolve((element).getChildren()); } else if (element instanceof TreeNode) { - var node = element; + var node = element; if (node.children) { resolve(node.children); } else { @@ -92,11 +90,11 @@ export class ServerTreeDataSource implements IDataSource { */ public getParent(tree: ITree, element: any): TPromise { if (element instanceof ConnectionProfile) { - return TPromise.as((element).getParent()); + return TPromise.as(element.getParent()); } else if (element instanceof ConnectionProfileGroup) { - return TPromise.as((element).getParent()); + return TPromise.as(element.getParent()); } else if (element instanceof TreeNode) { - return TPromise.as(TreeUpdateUtils.getObjectExplorerParent(element, this._connectionManagementService)); + return TPromise.as(TreeUpdateUtils.getObjectExplorerParent(element, this._connectionManagementService)); } else { return TPromise.as(null); } diff --git a/src/sql/parts/objectExplorer/viewlet/treeNodeContextKey.ts b/src/sql/parts/objectExplorer/viewlet/treeNodeContextKey.ts new file mode 100644 index 0000000000..85c05ab6ca --- /dev/null +++ b/src/sql/parts/objectExplorer/viewlet/treeNodeContextKey.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IConnectionProfile } from 'sqlops'; +import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; + +export class TreeNodeContextKey implements IContextKey { + + static NodeType = new RawContextKey('nodeType', undefined); + static SubType = new RawContextKey('nodeSubType', undefined); + static Status = new RawContextKey('nodeStatus', undefined); + static TreeNode = new RawContextKey('treeNode', undefined); + + private _nodeTypeKey: IContextKey; + private _subTypeKey: IContextKey; + private _statusKey: IContextKey; + private _treeNodeKey: IContextKey; + + constructor( + @IContextKeyService contextKeyService: IContextKeyService + ) { + this._nodeTypeKey = TreeNodeContextKey.NodeType.bindTo(contextKeyService); + this._subTypeKey = TreeNodeContextKey.SubType.bindTo(contextKeyService); + this._statusKey = TreeNodeContextKey.Status.bindTo(contextKeyService); + this._treeNodeKey = TreeNodeContextKey.TreeNode.bindTo(contextKeyService); + } + + set(value: TreeNode) { + this._treeNodeKey.set(value); + this._nodeTypeKey.set(value && value.nodeTypeId); + this._subTypeKey.set(value && value.nodeSubType); + this._statusKey.set(value && value.nodeStatus); + } + + reset(): void { + this._nodeTypeKey.reset(); + this._subTypeKey.reset(); + this._statusKey.reset(); + this._treeNodeKey.reset(); + } + + public get(): TreeNode { + return this._treeNodeKey.get(); + } +} diff --git a/src/sql/parts/objectExplorer/viewlet/treeSelectionHandler.ts b/src/sql/parts/objectExplorer/viewlet/treeSelectionHandler.ts index aec70e5062..af3a655565 100644 --- a/src/sql/parts/objectExplorer/viewlet/treeSelectionHandler.ts +++ b/src/sql/parts/objectExplorer/viewlet/treeSelectionHandler.ts @@ -104,7 +104,7 @@ export class TreeSelectionHandler { }); } } else if (isDoubleClick && selection && selection.length > 0 && (selection[0] instanceof TreeNode)) { - let treeNode = selection[0]; + let treeNode = selection[0]; if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) { connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode); if (connectionProfile) { diff --git a/src/sql/parts/objectExplorer/viewlet/treeUpdateUtils.ts b/src/sql/parts/objectExplorer/viewlet/treeUpdateUtils.ts index 8ce68a5327..353bd77441 100644 --- a/src/sql/parts/objectExplorer/viewlet/treeUpdateUtils.ts +++ b/src/sql/parts/objectExplorer/viewlet/treeUpdateUtils.ts @@ -13,6 +13,7 @@ import { NodeType } from 'sql/parts/objectExplorer/common/nodeType'; import { TPromise } from 'vs/base/common/winjs.base'; import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; import errors = require('vs/base/common/errors'); +import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; export class TreeUpdateUtils { @@ -113,7 +114,7 @@ export class TreeUpdateUtils { } public static connectIfNotConnected( - connection: ConnectionProfile, + connection: IConnectionProfile, options: IConnectionCompletionOptions, connectionManagementService: IConnectionManagementService, tree: ITree): TPromise { @@ -172,7 +173,7 @@ export class TreeUpdateUtils { * @param connectionManagementService Connection management service instance * @param objectExplorerService Object explorer service instance */ - public static connectAndCreateOeSession(connection: ConnectionProfile, options: IConnectionCompletionOptions, + public static connectAndCreateOeSession(connection: IConnectionProfile, options: IConnectionCompletionOptions, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: ITree): TPromise { return new TPromise((resolve, reject) => { TreeUpdateUtils.connectIfNotConnected(connection, options, connectionManagementService, tree).then(connectedConnection => { diff --git a/src/sql/parts/query/common/queryManagement.ts b/src/sql/parts/query/common/queryManagement.ts index 38f4a61f3e..7f8baa1c87 100644 --- a/src/sql/parts/query/common/queryManagement.ts +++ b/src/sql/parts/query/common/queryManagement.ts @@ -21,6 +21,7 @@ export interface IQueryManagementService { _serviceBrand: any; addQueryRequestHandler(queryType: string, runner: IQueryRequestHandler): IDisposable; + isProviderRegistered(providerId: string): boolean; registerRunner(runner: QueryRunner, uri: string): void; cancelQuery(ownerUri: string): Thenable; @@ -82,7 +83,6 @@ export interface IQueryRequestHandler { } export class QueryManagementService implements IQueryManagementService { - public static readonly DefaultQueryType = 'MSSQL'; public _serviceBrand: any; private _requestHandlers = new Map(); @@ -143,6 +143,11 @@ export class QueryManagementService implements IQueryManagementService { }; } + public isProviderRegistered(providerId: string): boolean { + let handler = this._requestHandlers.get(providerId); + return !!handler; + } + private addTelemetry(eventName: string, ownerUri: string, runOptions?: sqlops.ExecutionPlanOptions): void { let providerId: string = this._connectionService.getProviderIdFromUri(ownerUri); let data: TelemetryUtils.IConnectionTelemetryData = { diff --git a/src/sql/services/bootstrap/bootstrapParams.ts b/src/sql/services/bootstrap/bootstrapParams.ts index ce06e5b7db..d3f9d9521a 100644 --- a/src/sql/services/bootstrap/bootstrapParams.ts +++ b/src/sql/services/bootstrap/bootstrapParams.ts @@ -6,7 +6,7 @@ import { DataService } from 'sql/parts/grid/services/dataService'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ConnectionContextkey } from 'sql/parts/connection/common/connectionContextKey'; +import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey'; import { IBootstrapParams } from './bootstrapService'; export interface IQueryComponentParams extends IBootstrapParams { @@ -21,7 +21,7 @@ export interface IDefaultComponentParams extends IBootstrapParams { connection: IConnectionProfile; ownerUri: string; scopedContextService: IContextKeyService; - connectionContextKey: ConnectionContextkey; + connectionContextKey: ConnectionContextKey; } export interface IDashboardComponentParams extends IDefaultComponentParams { diff --git a/src/sql/services/common/commonServiceInterface.service.ts b/src/sql/services/common/commonServiceInterface.service.ts index fb7ee2bd21..8987dd5aca 100644 --- a/src/sql/services/common/commonServiceInterface.service.ts +++ b/src/sql/services/common/commonServiceInterface.service.ts @@ -17,7 +17,7 @@ import { IAdminService } from 'sql/parts/admin/common/adminService'; import { IQueryManagementService } from 'sql/parts/query/common/queryManagement'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { AngularDisposable } from 'sql/base/common/lifecycle'; -import { ConnectionContextkey } from 'sql/parts/connection/common/connectionContextKey'; +import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey'; import { ProviderMetadata, DatabaseInfo, SimpleExecuteResult } from 'sqlops'; @@ -47,7 +47,7 @@ export class SingleConnectionManagementService { constructor( private _connectionService: IConnectionManagementService, private _uri: string, - private _contextKey: ConnectionContextkey + private _contextKey: ConnectionContextKey ) { } public changeDatabase(name: string): Thenable { @@ -104,7 +104,7 @@ export class CommonServiceInterface extends AngularDisposable { protected _singleQueryManagementService: SingleQueryManagementService; public scopedContextKeyService: IContextKeyService; - protected _connectionContextKey: ConnectionContextkey; + protected _connectionContextKey: ConnectionContextKey; constructor( @Inject(IBootstrapParams) protected _params: IDefaultComponentParams, diff --git a/src/sql/services/scripting/scriptingService.ts b/src/sql/services/scripting/scriptingService.ts index 3573e3cac1..8e7db3a678 100644 --- a/src/sql/services/scripting/scriptingService.ts +++ b/src/sql/services/scripting/scriptingService.ts @@ -25,6 +25,11 @@ export interface IScriptingService { */ registerProvider(providerId: string, provider: sqlops.ScriptingProvider): void; + /** + * Specifies whether a provider with a given ID has been registered or not + */ + isProviderRegistered(providerId: string): boolean; + /** * Callback method for when scripting is complete */ @@ -99,6 +104,11 @@ export class ScriptingService implements IScriptingService { this._providers[providerId] = provider; } + public isProviderRegistered(providerId: string): boolean { + let provider = this._providers[providerId]; + return !!provider; + } + public dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 234ffb40ed..f34b83a131 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -973,4 +973,31 @@ declare module 'sqlops' { */ export function getProvidersByType(providerType: DataProviderType): T[]; } + + /** + * Context object passed as an argument to command callbacks. + * Defines the key properties required to identify a node in the object + * explorer tree and take action against it. + */ + export interface ObjectExplorerContext { + + /** + * The connection information for the selected object. + * Note that the connection is not guaranteed to be in a connected + * state on click. + */ + connectionProfile: IConnectionProfile; + /** + * Defines whether this is a Connection-level object. + * If not, the object is expected to be a child object underneath + * one of the connections. + */ + isConnectionNode: boolean; + /** + * Node info for objects below a specific connection. This + * may be null for a Connection-level object + */ + nodeInfo: NodeInfo; + } + } diff --git a/src/sqltest/parts/connection/connectionTreeActions.test.ts b/src/sqltest/parts/connection/connectionTreeActions.test.ts index c8f3510b62..ddaf618c76 100644 --- a/src/sqltest/parts/connection/connectionTreeActions.test.ts +++ b/src/sqltest/parts/connection/connectionTreeActions.test.ts @@ -50,7 +50,7 @@ suite('SQL Connection Tree Action tests', () => { errorMessageService.setup(x => x.showDialog(Severity.Error, TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => nothing); }); - function createConnectionManagementService(isConnectedReturnValue: boolean): TypeMoq.Mock { + function createConnectionManagementService(isConnectedReturnValue: boolean, profileToReturn: ConnectionProfile): TypeMoq.Mock { let connectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService, TypeMoq.MockBehavior.Strict); connectionManagementService.callBase = true; connectionManagementService.setup(x => x.isConnected(undefined, TypeMoq.It.isAny())).returns(() => isConnectedReturnValue); @@ -65,13 +65,15 @@ suite('SQL Connection Tree Action tests', () => { connectionManagementService.setup(x => x.onDisconnect).returns(() => new Emitter().event); 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); return connectionManagementService; } - function createObjectExplorerService(connectionManagementService: TestConnectionManagementService): TypeMoq.Mock { + function createObjectExplorerService(connectionManagementService: TestConnectionManagementService, getTreeNodeReturnVal: TreeNode): TypeMoq.Mock { let objectExplorerService = TypeMoq.Mock.ofType(ObjectExplorerService, TypeMoq.MockBehavior.Strict, connectionManagementService); objectExplorerService.callBase = true; + objectExplorerService.setup(x => x.getTreeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(getTreeNodeReturnVal)); objectExplorerService.setup(x => x.getObjectExplorerNode(TypeMoq.It.isAny())).returns(() => new TreeNode('', '', false, '', '', '', undefined, undefined, undefined)); objectExplorerService.setup(x => x.getObjectExplorerNode(undefined)).returns(() => new TreeNode('', '', false, '', '', '', undefined, undefined, undefined)); objectExplorerService.setup(x => x.onUpdateObjectExplorerNodes).returns(() => new Emitter().event); @@ -82,17 +84,6 @@ suite('SQL Connection Tree Action tests', () => { test('ManageConnectionAction - test if connect is called for manage action if not already connected', (done) => { let isConnectedReturnValue: boolean = false; - - let connectionManagementService = createConnectionManagementService(isConnectedReturnValue); - let objectExplorerService = createObjectExplorerService(connectionManagementService.object); - let treeSelectionMock = TypeMoq.Mock.ofType(TreeSelectionHandler); - let instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Loose); - instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny())).returns((input) => { - return treeSelectionMock.object; - }); - - let manageConnectionAction: ManageConnectionAction = new ManageConnectionAction(ManageConnectionAction.ID, - ManageConnectionAction.LABEL, connectionManagementService.object, instantiationService.object, objectExplorerService.object); let connection: ConnectionProfile = new ConnectionProfile(capabilitiesService, { savePassword: false, groupFullName: 'testGroup', @@ -109,17 +100,8 @@ suite('SQL Connection Tree Action tests', () => { saveProfile: true, id: 'testId' }); - var actionContext = new ObjectExplorerActionsContext(); - actionContext.connectionProfile = connection; - manageConnectionAction.run(actionContext).then((value) => { - connectionManagementService.verify(x => x.connect(TypeMoq.It.isAny(), undefined, TypeMoq.It.isAny(), undefined), TypeMoq.Times.once()); - }).then(() => done(), (err) => done(err)); - }); - - test('ManageConnectionAction - test if connect is called for manage action on database node if not already connected', (done) => { - let isConnectedReturnValue: boolean = false; - let connectionManagementService = createConnectionManagementService(isConnectedReturnValue); - let objectExplorerService = createObjectExplorerService(connectionManagementService.object); + let connectionManagementService = createConnectionManagementService(isConnectedReturnValue, connection); + let objectExplorerService = createObjectExplorerService(connectionManagementService.object, undefined); let treeSelectionMock = TypeMoq.Mock.ofType(TreeSelectionHandler); let instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Loose); instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny())).returns((input) => { @@ -127,7 +109,18 @@ suite('SQL Connection Tree Action tests', () => { }); let manageConnectionAction: ManageConnectionAction = new ManageConnectionAction(ManageConnectionAction.ID, - ManageConnectionAction.LABEL, connectionManagementService.object, instantiationService.object, objectExplorerService.object); + ManageConnectionAction.LABEL, undefined, connectionManagementService.object, capabilitiesService, instantiationService.object, objectExplorerService.object); + + var actionContext = new ObjectExplorerActionsContext(); + actionContext.connectionProfile = connection.toIConnectionProfile(); + actionContext.isConnectionNode = true; + manageConnectionAction.run(actionContext).then((value) => { + connectionManagementService.verify(x => x.connect(TypeMoq.It.isAny(), undefined, TypeMoq.It.isAny(), undefined), TypeMoq.Times.once()); + }).then(() => done(), (err) => done(err)); + }); + + test('ManageConnectionAction - test if connect is called for manage action on database node if not already connected', (done) => { + let isConnectedReturnValue: boolean = false; let connection: ConnectionProfile = new ConnectionProfile(capabilitiesService, { savePassword: false, groupFullName: 'testGroup', @@ -146,8 +139,20 @@ suite('SQL Connection Tree Action tests', () => { }); let treeNode = new TreeNode(NodeType.Database, 'db node', false, '', '', '', undefined, undefined, undefined); treeNode.connection = connection; + let connectionManagementService = createConnectionManagementService(isConnectedReturnValue, connection); + let objectExplorerService = createObjectExplorerService(connectionManagementService.object, treeNode); + let treeSelectionMock = TypeMoq.Mock.ofType(TreeSelectionHandler); + let instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Loose); + instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny())).returns((input) => { + return treeSelectionMock.object; + }); + + let manageConnectionAction: ManageConnectionAction = new ManageConnectionAction(ManageConnectionAction.ID, + ManageConnectionAction.LABEL, undefined, connectionManagementService.object, capabilitiesService, instantiationService.object, objectExplorerService.object); + var actionContext = new ObjectExplorerActionsContext(); - actionContext.treeNode = treeNode; + actionContext.connectionProfile = connection.toIConnectionProfile(); + actionContext.nodeInfo = treeNode.toNodeInfo(); manageConnectionAction.run(actionContext).then((value) => { connectionManagementService.verify(x => x.showDashboard(TypeMoq.It.isAny()), TypeMoq.Times.once()); }).then(() => done(), (err) => done(err)); @@ -156,10 +161,6 @@ suite('SQL Connection Tree Action tests', () => { test('DisconnectConnectionAction - test if disconnect is called when profile is connected', (done) => { let isConnectedReturnValue: boolean = true; - let connectionManagementService = createConnectionManagementService(isConnectedReturnValue); - let objectExplorerService = createObjectExplorerService(connectionManagementService.object); - - let changeConnectionAction: DisconnectConnectionAction = new DisconnectConnectionAction(DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL, connectionManagementService.object, objectExplorerService.object, errorMessageService.object); let connection: ConnectionProfile = new ConnectionProfile(capabilitiesService, { savePassword: false, groupFullName: 'testGroup', @@ -176,8 +177,13 @@ suite('SQL Connection Tree Action tests', () => { saveProfile: true, id: 'testId' }); + let connectionManagementService = createConnectionManagementService(isConnectedReturnValue, connection); + let objectExplorerService = createObjectExplorerService(connectionManagementService.object, undefined); + + let changeConnectionAction: DisconnectConnectionAction = new DisconnectConnectionAction(DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL, connection, connectionManagementService.object, objectExplorerService.object, errorMessageService.object); + var actionContext = new ObjectExplorerActionsContext(); - actionContext.connectionProfile = connection; + actionContext.connectionProfile = connection.toIConnectionProfile(); changeConnectionAction.run(actionContext).then((value) => { connectionManagementService.verify(x => x.isProfileConnected(TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce()); connectionManagementService.verify(x => x.disconnect(TypeMoq.It.isAny()), TypeMoq.Times.once()); @@ -185,7 +191,7 @@ suite('SQL Connection Tree Action tests', () => { }); test('AddServerAction - test if show connection dialog is called', (done) => { - let connectionManagementService = createConnectionManagementService(true); + let connectionManagementService = createConnectionManagementService(true, undefined); let connectionTreeAction: AddServerAction = new AddServerAction(AddServerAction.ID, AddServerAction.LABEL, connectionManagementService.object); let conProfGroup = new ConnectionProfileGroup('testGroup', undefined, 'testGroup', undefined, undefined); @@ -195,7 +201,7 @@ suite('SQL Connection Tree Action tests', () => { }); test('ActiveConnectionsFilterAction - test if view is called to display filtered results', (done) => { - let connectionManagementService = createConnectionManagementService(true); + let connectionManagementService = createConnectionManagementService(true, undefined); let instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Loose); instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny())).returns((input) => { @@ -212,7 +218,7 @@ suite('SQL Connection Tree Action tests', () => { }); test('ActiveConnectionsFilterAction - test if view is called refresh results if action is toggled', (done) => { - let connectionManagementService = createConnectionManagementService(true); + let connectionManagementService = createConnectionManagementService(true, undefined); let instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Loose); instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny())).returns((input) => { @@ -230,7 +236,7 @@ suite('SQL Connection Tree Action tests', () => { }); test('RecentConnectionsFilterAction - test if view is called to display filtered results', (done) => { - let connectionManagementService = createConnectionManagementService(true); + let connectionManagementService = createConnectionManagementService(true, undefined); let instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Loose); instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny())).returns((input) => { @@ -247,7 +253,7 @@ suite('SQL Connection Tree Action tests', () => { }); test('RecentConnectionsFilterAction - test if view is called refresh results if action is toggled', (done) => { - let connectionManagementService = createConnectionManagementService(true); + let connectionManagementService = createConnectionManagementService(true, undefined); let instantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Loose); instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny())).returns((input) => { @@ -265,7 +271,7 @@ suite('SQL Connection Tree Action tests', () => { }); test('DeleteConnectionAction - test delete connection', (done) => { - let connectionManagementService = createConnectionManagementService(true); + let connectionManagementService = createConnectionManagementService(true, undefined); let connection: ConnectionProfile = new ConnectionProfile(capabilitiesService, { savePassword: false, @@ -296,7 +302,7 @@ suite('SQL Connection Tree Action tests', () => { test('DeleteConnectionAction - test delete connection group', (done) => { let isConnectedReturnValue: boolean = false; - let connectionManagementService = createConnectionManagementService(isConnectedReturnValue); + let connectionManagementService = createConnectionManagementService(isConnectedReturnValue, undefined); let conProfGroup = new ConnectionProfileGroup('testGroup', undefined, 'testGroup', undefined, undefined); let connectionAction: DeleteConnectionAction = new DeleteConnectionAction(DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, @@ -311,7 +317,7 @@ suite('SQL Connection Tree Action tests', () => { test('DeleteConnectionAction - delete should not be called if connect is an unsaved connection', (done) => { let isConnectedReturnValue: boolean = false; - let connectionManagementService = createConnectionManagementService(isConnectedReturnValue); + let connectionManagementService = createConnectionManagementService(isConnectedReturnValue, undefined); let connection: ConnectionProfile = new ConnectionProfile(capabilitiesService, { savePassword: false, diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index c2773f51b3..32902186ce 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -60,6 +60,8 @@ export class MenuId { static readonly ViewItemContext = new MenuId(); static readonly TouchBarContext = new MenuId(); static readonly SearchContext = new MenuId(); + // {{SQL CARBON EDIT}} + static readonly ObjectExplorerItemContext = new MenuId(); readonly id: string = String(MenuId.ID++); } diff --git a/src/vs/workbench/services/actions/electron-browser/menusExtensionPoint.ts b/src/vs/workbench/services/actions/electron-browser/menusExtensionPoint.ts index b9255ebc53..0b615e5327 100644 --- a/src/vs/workbench/services/actions/electron-browser/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/electron-browser/menusExtensionPoint.ts @@ -40,6 +40,8 @@ namespace schema { case 'scm/change/title': return MenuId.SCMChangeContext; case 'view/title': return MenuId.ViewTitle; case 'view/item/context': return MenuId.ViewItemContext; + // {{SQL CARBON EDIT}} + case 'objectExplorer/item/context': return MenuId.ObjectExplorerItemContext; } return void 0;