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

@@ -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<TestConnectionManagementService> {
function createConnectionManagementService(isConnectedReturnValue: boolean, profileToReturn: ConnectionProfile): TypeMoq.Mock<TestConnectionManagementService> {
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<any>().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<ObjectExplorerService> {
function createObjectExplorerService(connectionManagementService: TestConnectionManagementService, getTreeNodeReturnVal: TreeNode): TypeMoq.Mock<ObjectExplorerService> {
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<ObjectExplorerNodeEventArgs>().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,