Extensibility: Context menu support in Object Explorer (#1883)

- Fixes #1867 context menu should be extensible
- Added context keys to support "when" conditions on the new extensions
- Fixes issue where actions like New Query, scripting show up even if these are not valid for the provider type or object type
- Fixed node expansion bug where rapid connect / expand / disconnect could break the app (fix in ObjectExplorerService.onNodeExpanded)
- Major change to how internal actions work. These cannot assume the context has non-serializable objects. Opened up some APIs to make this easier to handle.
- Fixed a number of existing bugs in internal actions.
  - Notably, DisconnectAction was adding a listener on each right-click on an active connection and never getting it disposed. This wasn't needed at all due to design changes.
  - Another bug fix is that the Manage action now correctly navigates to the DB dashboard for database-level connections. Before this it went to the server-level dashboard.

* Define API for context info
This commit is contained in:
Kevin Cunnane
2018-07-10 12:23:47 -07:00
committed by GitHub
parent 0f0b959e14
commit d51a7a9eb7
24 changed files with 397 additions and 333 deletions

View File

@@ -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}`);
}
}

View File

@@ -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<any> {
return new TPromise<boolean>((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 = <ConnectionProfile>element;
let parent: ConnectionProfileGroup = element.parent;
if (parent && parent.id === Constants.unsavedGroupId) {
this.enabled = false;

View File

@@ -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<TreeNode> {
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<boolean> {
public async run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
let profile: IConnectionProfile;
if (actionContext.connectionProfile) {
profile = actionContext.connectionProfile;
} else {
profile = TreeUpdateUtils.getConnectionProfile(<TreeNode>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<any> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
this._treeSelectionHandler.onTreeActionStateChange(true);
let self = this;
let promise = new TPromise<boolean>((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<boolean> {
private async doManage(actionContext: ObjectExplorerActionsContext): TPromise<boolean> {
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(<TreeNode>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<boolean> {
public async run(actionContext: any): TPromise<boolean> {
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(<TreeNode>this._objectExplorerTreeNode);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode);
var ownerUri = this._connectionManagementService.getConnectionId(connectionProfile);
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
var metadata = (<TreeNode>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<boolean> {
public async run(actionContext: any): TPromise<boolean> {
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(<TreeNode>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<boolean> {
public async run(actionContext: any): TPromise<boolean> {
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(<TreeNode>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<boolean> {
public async run(actionContext: any): TPromise<boolean> {
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(<TreeNode>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<boolean> {
public async run(actionContext: any): TPromise<boolean> {
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(<TreeNode>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<boolean> {
public async run(actionContext: any): TPromise<boolean> {
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(<TreeNode>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<boolean> {
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 = (<TreeNode>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<NodeType, any[]> {
let scriptMap = new Map<NodeType, any[]>();

View File

@@ -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 = <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<NodeType, any[]> = 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<NodeType, any[]> = 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;
}

View File

@@ -73,26 +73,21 @@ export class ServerTreeController extends treedefaults.DefaultController {
event.stopPropagation();
tree.setFocus(element);
let parent: ConnectionProfileGroup = undefined;
if (element instanceof ConnectionProfileGroup) {
parent = <ConnectionProfileGroup>element;
}
else if (element instanceof ConnectionProfile) {
parent = (<ConnectionProfile>element).parent;
}
var actionContext: any;
if (element instanceof TreeNode) {
actionContext = new ObjectExplorerActionsContext();
actionContext.container = event.target;
actionContext.treeNode = <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 = <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;
}

View File

@@ -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 (<ConnectionProfile>element).id;
} else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).id;
} else if (element instanceof TreeNode) {
return (<TreeNode>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 (<ConnectionProfileGroup>element).hasChildren();
return element.hasChildren();
} else if (element instanceof TreeNode) {
return !(<TreeNode>element).isAlwaysLeaf;
return !element.isAlwaysLeaf;
}
return false;
}
@@ -70,7 +68,7 @@ export class ServerTreeDataSource implements IDataSource {
} else if (element instanceof ConnectionProfileGroup) {
resolve((<ConnectionProfileGroup>element).getChildren());
} else if (element instanceof TreeNode) {
var node = <TreeNode>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<any> {
if (element instanceof ConnectionProfile) {
return TPromise.as((<ConnectionProfile>element).getParent());
return TPromise.as(element.getParent());
} else if (element instanceof ConnectionProfileGroup) {
return TPromise.as((<ConnectionProfileGroup>element).getParent());
return TPromise.as(element.getParent());
} else if (element instanceof TreeNode) {
return TPromise.as(TreeUpdateUtils.getObjectExplorerParent(<TreeNode>element, this._connectionManagementService));
return TPromise.as(TreeUpdateUtils.getObjectExplorerParent(element, this._connectionManagementService));
} else {
return TPromise.as(null);
}

View File

@@ -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<TreeNode> {
static NodeType = new RawContextKey<string>('nodeType', undefined);
static SubType = new RawContextKey<string>('nodeSubType', undefined);
static Status = new RawContextKey<string>('nodeStatus', undefined);
static TreeNode = new RawContextKey<TreeNode>('treeNode', undefined);
private _nodeTypeKey: IContextKey<string>;
private _subTypeKey: IContextKey<string>;
private _statusKey: IContextKey<string>;
private _treeNodeKey: IContextKey<TreeNode>;
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();
}
}

View File

@@ -104,7 +104,7 @@ export class TreeSelectionHandler {
});
}
} else if (isDoubleClick && selection && selection.length > 0 && (selection[0] instanceof TreeNode)) {
let treeNode = <TreeNode>selection[0];
let treeNode = selection[0];
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
if (connectionProfile) {

View File

@@ -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<ConnectionProfile> {
@@ -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<boolean> {
return new TPromise<boolean>((resolve, reject) => {
TreeUpdateUtils.connectIfNotConnected(connection, options, connectionManagementService, tree).then(connectedConnection => {