From a63800deb1a7b8a4476bc3bafd2bf664ad800f97 Mon Sep 17 00:00:00 2001 From: Matt Irvine Date: Wed, 24 Jan 2018 10:11:34 -0800 Subject: [PATCH] Add global current connection (#505) --- .../dashboard/widgets/insights/actions.ts | 8 +- .../common/objectExplorerService.ts | 49 ++++++++ .../viewlet/connectionTreeAction.ts | 7 +- .../viewlet/connectionViewlet.ts | 5 +- .../viewlet/objectExplorerActions.ts | 7 +- .../viewlet/serverTreeView.ts | 14 +++ src/sql/workbench/common/actions.ts | 10 +- src/sql/workbench/common/taskUtilities.ts | 50 +++++++- .../connection/objectExplorerService.test.ts | 54 +++++++++ .../workbench/common/taskUtilities.test.ts | 113 ++++++++++++++++++ .../browser/parts/editor/tabsTitleControl.ts | 12 +- .../parts/files/browser/fileActions.ts | 12 +- 12 files changed, 326 insertions(+), 15 deletions(-) create mode 100644 src/sqltest/workbench/common/taskUtilities.test.ts diff --git a/src/sql/parts/dashboard/widgets/insights/actions.ts b/src/sql/parts/dashboard/widgets/insights/actions.ts index 6c38407e01..1a9ab2ba6c 100644 --- a/src/sql/parts/dashboard/widgets/insights/actions.ts +++ b/src/sql/parts/dashboard/widgets/insights/actions.ts @@ -10,6 +10,8 @@ import * as TaskUtilities from 'sql/workbench/common/taskUtilities'; import { RunQueryOnConnectionMode, IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { InsightActionContext } from 'sql/workbench/common/actions'; +import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; export class RunInsightQueryAction extends Action { public static ID = 'runQuery'; @@ -18,7 +20,9 @@ export class RunInsightQueryAction extends Action { constructor( id: string, label: string, @IQueryEditorService protected _queryEditorService: IQueryEditorService, - @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService + @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, + @IObjectExplorerService protected _objectExplorerService: IObjectExplorerService, + @IWorkbenchEditorService protected _workbenchEditorService: IWorkbenchEditorService ) { super(id, label); } @@ -29,6 +33,8 @@ export class RunInsightQueryAction extends Action { context.profile, this._connectionManagementService, this._queryEditorService, + this._objectExplorerService, + this._workbenchEditorService, context.insight.query as string, RunQueryOnConnectionMode.executeQuery ).then(() => resolve(true), () => resolve(false)); diff --git a/src/sql/parts/registeredServer/common/objectExplorerService.ts b/src/sql/parts/registeredServer/common/objectExplorerService.ts index 271c94f5e7..eb22cd27c0 100644 --- a/src/sql/parts/registeredServer/common/objectExplorerService.ts +++ b/src/sql/parts/registeredServer/common/objectExplorerService.ts @@ -18,6 +18,7 @@ import * as TelemetryKeys from 'sql/common/telemetryKeys'; import * as TelemetryUtils from 'sql/common/telemetryUtilities'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { warn, error } from 'sql/base/common/log'; +import { ServerTreeView } from 'sql/parts/registeredServer/viewlet/serverTreeView'; export const SERVICE_ID = 'ObjectExplorerService'; @@ -54,6 +55,12 @@ export interface IObjectExplorerService { deleteObjectExplorerNode(connection: IConnectionProfile): void; onUpdateObjectExplorerNodes: Event; + + registerServerTreeView(view: ServerTreeView): void; + + getSelectedProfileAndDatabase(): { profile: ConnectionProfile, databaseName: string }; + + isFocused(): boolean; } interface SessionStatus { @@ -85,6 +92,8 @@ export class ObjectExplorerService implements IObjectExplorerService { private _onUpdateObjectExplorerNodes: Emitter; + private _serverTreeView: ServerTreeView; + constructor( @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @ITelemetryService private _telemetryService: ITelemetryService @@ -355,4 +364,44 @@ export class ObjectExplorerService implements IObjectExplorerService { return new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath, nodeInfo.nodeSubType, nodeInfo.nodeStatus, parent, nodeInfo.metadata); } + + public registerServerTreeView(view: ServerTreeView): void { + if (this._serverTreeView) { + throw new Error('The object explorer server tree view is already registered'); + } + this._serverTreeView = view; + } + + /** + * Returns the connection profile corresponding to the current Object Explorer selection, + * or undefined if there are multiple selections or no such connection + */ + public getSelectedProfileAndDatabase(): { profile: ConnectionProfile, databaseName: string } { + if (!this._serverTreeView) { + return undefined; + } + let selection = this._serverTreeView.getSelection(); + if (selection.length === 1) { + let selectedNode = selection[0]; + if (selectedNode instanceof ConnectionProfile) { + return { profile: selectedNode, databaseName: undefined }; + } else if (selectedNode instanceof TreeNode) { + let profile = selectedNode.getConnectionProfile(); + let database = selectedNode.getDatabaseName(); + // If the database is unavailable, use the server connection + if (selectedNode.nodeTypeId === 'Database' && selectedNode.isAlwaysLeaf) { + database = undefined; + } + return { profile: profile, databaseName: database }; + } + } + return undefined; + } + + /** + * Returns a boolean indicating whether the Object Explorer tree has focus + */ + public isFocused(): boolean { + return this._serverTreeView.isFocused(); + } } \ No newline at end of file diff --git a/src/sql/parts/registeredServer/viewlet/connectionTreeAction.ts b/src/sql/parts/registeredServer/viewlet/connectionTreeAction.ts index 5eba123e39..18b54a1da6 100644 --- a/src/sql/parts/registeredServer/viewlet/connectionTreeAction.ts +++ b/src/sql/parts/registeredServer/viewlet/connectionTreeAction.ts @@ -21,6 +21,7 @@ import { IObjectExplorerService } from 'sql/parts/registeredServer/common/object import { TreeNode } from 'sql/parts/registeredServer/common/treeNode'; import Severity from 'vs/base/common/severity'; import { ObjectExplorerActionsContext, ObjectExplorerActionUtilities } from 'sql/parts/registeredServer/viewlet/objectExplorerActions'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; export class RefreshAction extends Action { @@ -373,7 +374,9 @@ export class NewQueryAction extends Action { id: string, label: string, @IQueryEditorService private queryEditorService: IQueryEditorService, - @IConnectionManagementService private connectionManagementService: IConnectionManagementService + @IConnectionManagementService private connectionManagementService: IConnectionManagementService, + @IObjectExplorerService protected _objectExplorerService: IObjectExplorerService, + @IWorkbenchEditorService protected _workbenchEditorService: IWorkbenchEditorService ) { super(id, label); this.class = 'extension-action update'; @@ -384,7 +387,7 @@ export class NewQueryAction extends Action { this._connectionProfile = actionContext.connectionProfile; } - TaskUtilities.newQuery(this._connectionProfile, this.connectionManagementService, this.queryEditorService); + TaskUtilities.newQuery(this._connectionProfile, this.connectionManagementService, this.queryEditorService, this._objectExplorerService, this._workbenchEditorService); return TPromise.as(true); } } diff --git a/src/sql/parts/registeredServer/viewlet/connectionViewlet.ts b/src/sql/parts/registeredServer/viewlet/connectionViewlet.ts index 9aaf6cc4c5..99d3590336 100644 --- a/src/sql/parts/registeredServer/viewlet/connectionViewlet.ts +++ b/src/sql/parts/registeredServer/viewlet/connectionViewlet.ts @@ -26,6 +26,7 @@ import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ClearSearchAction, AddServerAction, AddServerGroupAction, ActiveConnectionsFilterAction } from 'sql/parts/registeredServer/viewlet/connectionTreeAction'; import { warn } from 'sql/base/common/log'; +import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService'; export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet { @@ -48,7 +49,8 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet { @IConnectionManagementService private connectionManagementService: IConnectionManagementService, @IInstantiationService private _instantiationService: IInstantiationService, @IViewletService private viewletService: IViewletService, - @IMessageService private messageService: IMessageService + @IMessageService private messageService: IMessageService, + @IObjectExplorerService private objectExplorerService: IObjectExplorerService ) { super(VIEWLET_ID, telemetryService, _themeService); this._searchDelayer = new ThrottledDelayer(500); @@ -62,6 +64,7 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet { AddServerGroupAction.LABEL); this._serverTreeView = this._instantiationService.createInstance(ServerTreeView); this._activeConnectionsFilterAction = this._serverTreeView.activeConnectionsFilterAction; + this.objectExplorerService.registerServerTreeView(this._serverTreeView); } private onError(err: any): void { diff --git a/src/sql/parts/registeredServer/viewlet/objectExplorerActions.ts b/src/sql/parts/registeredServer/viewlet/objectExplorerActions.ts index 3a60d21e8e..1aee95b6f2 100644 --- a/src/sql/parts/registeredServer/viewlet/objectExplorerActions.ts +++ b/src/sql/parts/registeredServer/viewlet/objectExplorerActions.ts @@ -21,6 +21,7 @@ import { IScriptingService } from 'sql/services/scripting/scriptingService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService'; import * as Constants from 'sql/parts/connection/common/constants'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; export class ObjectExplorerActionsContext { public treeNode: TreeNode; @@ -39,9 +40,11 @@ export class OENewQueryAction extends NewQueryAction { id: string, label: string, icon: string, @IQueryEditorService protected _queryEditorService: IQueryEditorService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, - @IInstantiationService private _instantiationService: IInstantiationService + @IInstantiationService private _instantiationService: IInstantiationService, + @IObjectExplorerService protected _objectExplorerService: IObjectExplorerService, + @IWorkbenchEditorService protected _workbenchEditorService: IWorkbenchEditorService ) { - super(id, label, icon, _queryEditorService, _connectionManagementService); + super(id, label, icon, _queryEditorService, _connectionManagementService, _objectExplorerService, _workbenchEditorService); } public run(actionContext: any): TPromise { diff --git a/src/sql/parts/registeredServer/viewlet/serverTreeView.ts b/src/sql/parts/registeredServer/viewlet/serverTreeView.ts index 7bdad042e5..81f1bea50d 100644 --- a/src/sql/parts/registeredServer/viewlet/serverTreeView.ts +++ b/src/sql/parts/registeredServer/viewlet/serverTreeView.ts @@ -406,6 +406,20 @@ export class ServerTreeView { } } + /** + * Get the list of selected nodes in the tree + */ + public getSelection(): any[] { + return this._tree.getSelection(); + } + + /** + * Get whether the tree view currently has focus + */ + public isFocused(): boolean { + return this._tree.isDOMFocused(); + } + /** * dispose the server tree view */ diff --git a/src/sql/workbench/common/actions.ts b/src/sql/workbench/common/actions.ts index 952f7b0b90..7712c1837e 100644 --- a/src/sql/workbench/common/actions.ts +++ b/src/sql/workbench/common/actions.ts @@ -24,6 +24,8 @@ import { Action } from 'vs/base/common/actions'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; +import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; export interface BaseActionContext { object?: ObjectMetadata; @@ -47,7 +49,9 @@ export class NewQueryAction extends TaskAction { constructor( id: string, label: string, icon: string, @IQueryEditorService protected _queryEditorService: IQueryEditorService, - @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService + @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, + @IObjectExplorerService protected _objectExplorerService: IObjectExplorerService, + @IWorkbenchEditorService protected _workbenchEditorService: IWorkbenchEditorService ) { super(id, label, icon); } @@ -57,7 +61,9 @@ export class NewQueryAction extends TaskAction { TaskUtilities.newQuery( actionContext.profile, this._connectionManagementService, - this._queryEditorService + this._queryEditorService, + this._objectExplorerService, + this._workbenchEditorService ).then( result => { resolve(true); diff --git a/src/sql/workbench/common/taskUtilities.ts b/src/sql/workbench/common/taskUtilities.ts index 1d620b46b5..304c4ac27c 100644 --- a/src/sql/workbench/common/taskUtilities.ts +++ b/src/sql/workbench/common/taskUtilities.ts @@ -24,6 +24,10 @@ import data = require('data'); import nls = require('vs/nls'); import os = require('os'); import path = require('path'); +import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { QueryInput } from 'sql/parts/query/common/queryInput'; +import { DashboardInput } from 'sql/parts/dashboard/dashboardInput'; // map for the version of SQL Server (default is 140) const scriptCompatibilityOptionMap = { @@ -237,10 +241,15 @@ export function newQuery( connectionProfile: IConnectionProfile, connectionService: IConnectionManagementService, queryEditorService: IQueryEditorService, + objectExplorerService: IObjectExplorerService, + workbenchEditorService: IWorkbenchEditorService, sqlContent?: string, executeOnOpen: RunQueryOnConnectionMode = RunQueryOnConnectionMode.none ): Promise { return new Promise((resolve) => { + if (!connectionProfile) { + connectionProfile = getCurrentGlobalConnection(objectExplorerService, connectionService, workbenchEditorService); + } queryEditorService.newSqlEditor(sqlContent).then((owner: IConnectableInput) => { // Connect our editor to the input connection let options: IConnectionCompletionOptions = { @@ -250,9 +259,13 @@ export function newQuery( showConnectionDialogOnError: true, showFirewallRuleOnError: true }; - connectionService.connect(connectionProfile, owner.uri, options).then(() => { + if (connectionProfile) { + connectionService.connect(connectionProfile, owner.uri, options).then(() => { + resolve(); + }); + } else { resolve(); - }); + } }); }); } @@ -331,6 +344,39 @@ export function openInsight(query: IInsightsConfig, profile: IConnectionProfile, insightDialogService.show(query, profile); } +/** + * Get the current global connection, which is the connection from the active editor, unless OE + * is focused or there is no such editor, in which case it comes from the OE selection. Returns + * undefined when there is no such connection. +*/ +export function getCurrentGlobalConnection(objectExplorerService: IObjectExplorerService, connectionManagementService: IConnectionManagementService, workbenchEditorService: IWorkbenchEditorService): IConnectionProfile { + let connection: IConnectionProfile; + + let objectExplorerSelection = objectExplorerService.getSelectedProfileAndDatabase(); + if (objectExplorerSelection) { + let objectExplorerProfile = objectExplorerSelection.profile; + if (connectionManagementService.isProfileConnected(objectExplorerProfile)) { + if (objectExplorerSelection.databaseName) { + connection = objectExplorerProfile.cloneWithDatabase(objectExplorerSelection.databaseName); + } else { + connection = objectExplorerProfile; + } + } + if (objectExplorerService.isFocused()) { + return connection; + } + } + + let activeInput = workbenchEditorService.getActiveEditorInput(); + if (activeInput) { + if (activeInput instanceof QueryInput || activeInput instanceof EditDataInput || activeInput instanceof DashboardInput) { + connection = connectionManagementService.getConnectionProfile(activeInput.uri); + } + } + + return connection; +} + /* Helper Methods */ function getStartPos(script: string, operation: ScriptOperation, typeName: string): number { let objectTypeName = objectScriptMap[typeName]; diff --git a/src/sqltest/parts/connection/objectExplorerService.test.ts b/src/sqltest/parts/connection/objectExplorerService.test.ts index 2881f5100a..01f5ddafce 100644 --- a/src/sqltest/parts/connection/objectExplorerService.test.ts +++ b/src/sqltest/parts/connection/objectExplorerService.test.ts @@ -16,6 +16,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as data from 'data'; import * as TypeMoq from 'typemoq'; import * as assert from 'assert'; +import { ServerTreeView } from 'sql/parts/registeredServer/viewlet/serverTreeView'; import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes'; suite('SQL Object Explorer Service tests', () => { @@ -448,4 +449,57 @@ suite('SQL Object Explorer Service tests', () => { assert.equal(table1Node.getConnectionProfile(), connection); assert.equal(table1Node.getDatabaseName(), 'Db1'); }); + + test('getSelectedProfileAndDatabase returns the profile if it is selected', () => { + let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined } as ServerTreeView); + serverTreeView.setup(x => x.getSelection()).returns(() => [connection]); + objectExplorerService.registerServerTreeView(serverTreeView.object); + + let selectedProfileAndDatabase = objectExplorerService.getSelectedProfileAndDatabase(); + assert.equal(selectedProfileAndDatabase.profile, connection); + assert.equal(selectedProfileAndDatabase.databaseName, undefined); + }); + + test('getSelectedProfileAndDatabase returns the profile but no database if children of a server are selected', () => { + let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined } as ServerTreeView); + let databaseNode = new TreeNode(NodeType.Folder, 'Folder1', false, 'testServerName\\Folder1', '', '', undefined, undefined); + databaseNode.connection = connection; + serverTreeView.setup(x => x.getSelection()).returns(() => [databaseNode]); + objectExplorerService.registerServerTreeView(serverTreeView.object); + + let selectedProfileAndDatabase = objectExplorerService.getSelectedProfileAndDatabase(); + assert.equal(selectedProfileAndDatabase.profile, connection); + assert.equal(selectedProfileAndDatabase.databaseName, undefined); + }); + + test('getSelectedProfileAndDatabase returns the profile and database if children of a database node are selected', () => { + let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined } as ServerTreeView); + let databaseMetadata = { + metadataType: 0, + metadataTypeName: 'Database', + urn: '//server/db1/', + name: 'Db1', + schema: undefined + }; + let databaseName = 'Db1'; + let databaseNode = new TreeNode(NodeType.Database, databaseName, false, 'testServerName\\Db1', '', '', undefined, databaseMetadata); + let tablesNode = new TreeNode(NodeType.Folder, 'Tables', false, 'testServerName\\Db1\\tables', '', '', databaseNode, undefined); + databaseNode.connection = connection; + databaseNode.children = [tablesNode]; + serverTreeView.setup(x => x.getSelection()).returns(() => [tablesNode]); + objectExplorerService.registerServerTreeView(serverTreeView.object); + + let selectedProfileAndDatabase = objectExplorerService.getSelectedProfileAndDatabase(); + assert.equal(selectedProfileAndDatabase.profile, connection); + assert.equal(selectedProfileAndDatabase.databaseName, databaseName); + }); + + test('getSelectedProfileAndDatabase returns undefined when there is no selection', () => { + let serverTreeView = TypeMoq.Mock.ofInstance({ getSelection: () => undefined } as ServerTreeView); + serverTreeView.setup(x => x.getSelection()).returns(() => []); + objectExplorerService.registerServerTreeView(serverTreeView.object); + + let selectedProfileAndDatabase = objectExplorerService.getSelectedProfileAndDatabase(); + assert.equal(selectedProfileAndDatabase, undefined); + }); }); \ No newline at end of file diff --git a/src/sqltest/workbench/common/taskUtilities.test.ts b/src/sqltest/workbench/common/taskUtilities.test.ts new file mode 100644 index 0000000000..92855cf2ef --- /dev/null +++ b/src/sqltest/workbench/common/taskUtilities.test.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as assert from 'assert'; +import * as TypeMoq from 'typemoq'; +import * as TaskUtilities from 'sql/workbench/common/taskUtilities'; +import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService'; +import { TestConnectionManagementService } from 'sqltest/stubs/connectionManagementService.test'; +import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; +import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; +import { WorkbenchEditorTestService } from 'sqltest/stubs/workbenchEditorTestService'; +import URI from 'vs/base/common/uri'; +import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; +import { QueryInput } from 'sql/parts/query/common/queryInput'; + +suite('TaskUtilities', function() { + test('getCurrentGlobalConnection returns the selected OE server if a server or one of its children is selected', () => { + let connectionProfile = { databaseName: 'test_database', id: 'test_id', authenticationType: 'SQL Login', password: 'test_password', serverName: 'test_server', userName: 'test_user' } as IConnectionProfile; + let mockObjectExplorerService = TypeMoq.Mock.ofInstance({isFocused: () => undefined, getSelectedProfileAndDatabase: () => undefined } as IObjectExplorerService); + let mockConnectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService); + let mockWorkbenchEditorService = TypeMoq.Mock.ofType(WorkbenchEditorTestService); + let expectedProfile = new ConnectionProfile(undefined, connectionProfile); + mockObjectExplorerService.setup(x => x.isFocused()).returns(() => true); + mockObjectExplorerService.setup(x => x.getSelectedProfileAndDatabase()).returns(() => { + return { profile: expectedProfile, databaseName: undefined }; + }); + mockConnectionManagementService.setup(x => x.isProfileConnected(TypeMoq.It.is(profile => profile === expectedProfile))).returns(() => true); + mockWorkbenchEditorService.setup(x => x.getActiveEditorInput()).returns(() => undefined); + + // If I call getCurrentGlobalConnection, it should return the expected server profile + let actualProfile = TaskUtilities.getCurrentGlobalConnection(mockObjectExplorerService.object, mockConnectionManagementService.object, mockWorkbenchEditorService.object); + assert.equal(actualProfile, expectedProfile); + }); + + test('getCurrentGlobalConnection returns the selected OE database if a database or its children is selected', () => { + let connectionProfile = { databaseName: 'test_database', id: 'test_id', authenticationType: 'SQL Login', password: 'test_password', serverName: 'test_server', userName: 'test_user' } as IConnectionProfile; + let mockObjectExplorerService = TypeMoq.Mock.ofInstance({isFocused: () => undefined, getSelectedProfileAndDatabase: () => undefined } as IObjectExplorerService); + let mockConnectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService); + let mockWorkbenchEditorService = TypeMoq.Mock.ofType(WorkbenchEditorTestService); + let serverProfile = new ConnectionProfile(undefined, connectionProfile); + let dbName = 'test_database'; + mockObjectExplorerService.setup(x => x.isFocused()).returns(() => true); + mockObjectExplorerService.setup(x => x.getSelectedProfileAndDatabase()).returns(() => { + return { profile: serverProfile, databaseName: dbName }; + }); + mockConnectionManagementService.setup(x => x.isProfileConnected(TypeMoq.It.is(profile => profile === serverProfile))).returns(() => true); + mockWorkbenchEditorService.setup(x => x.getActiveEditorInput()).returns(() => undefined); + + // If I call getCurrentGlobalConnection, it should return the expected database profile + let actualProfile = TaskUtilities.getCurrentGlobalConnection(mockObjectExplorerService.object, mockConnectionManagementService.object, mockWorkbenchEditorService.object); + assert.equal(actualProfile.databaseName, dbName); + assert.notEqual(actualProfile.id, serverProfile.id); + // Other connection attributes still match + assert.equal(actualProfile.authenticationType, serverProfile.authenticationType); + assert.equal(actualProfile.password, serverProfile.password); + assert.equal(actualProfile.serverName, serverProfile.serverName); + assert.equal(actualProfile.userName, serverProfile.userName); + }); + + test('getCurrentGlobalConnection returns the connection from the active tab, if there is one and OE is not focused', () => { + let connectionProfile = { databaseName: 'test_database', id: 'test_id', authenticationType: 'SQL Login', password: 'test_password', serverName: 'test_server', userName: 'test_user' } as IConnectionProfile; + let mockObjectExplorerService = TypeMoq.Mock.ofInstance({isFocused: () => undefined, getSelectedProfileAndDatabase: () => undefined } as IObjectExplorerService); + let mockConnectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService); + let mockWorkbenchEditorService = TypeMoq.Mock.ofType(WorkbenchEditorTestService); + let oeProfile = new ConnectionProfile(undefined, connectionProfile); + let connectionProfile2 = Object.assign({}, connectionProfile); + connectionProfile2.serverName = 'test_server_2'; + connectionProfile2.id = 'test_id_2'; + let tabProfile = new ConnectionProfile(undefined, connectionProfile2); + mockObjectExplorerService.setup(x => x.isFocused()).returns(() => false); + mockObjectExplorerService.setup(x => x.getSelectedProfileAndDatabase()).returns(() => { + return { profile: oeProfile, databaseName: undefined }; + }); + mockConnectionManagementService.setup(x => x.isProfileConnected(TypeMoq.It.is(profile => profile === oeProfile || profile === tabProfile))).returns(() => true); + + // Mock the workbench service to return the active tab connection + let tabConnectionUri = 'test_uri'; + let editorInput = new UntitledEditorInput(URI.parse(tabConnectionUri), false, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + let queryInput = new QueryInput(undefined, undefined, editorInput, undefined, undefined, undefined, undefined, undefined); + mockWorkbenchEditorService.setup(x => x.getActiveEditorInput()).returns(() => queryInput); + mockConnectionManagementService.setup(x => x.getConnectionProfile(tabConnectionUri)).returns(() => tabProfile); + + // If I call getCurrentGlobalConnection, it should return the expected profile from the active tab + let actualProfile = TaskUtilities.getCurrentGlobalConnection(mockObjectExplorerService.object, mockConnectionManagementService.object, mockWorkbenchEditorService.object); + assert.equal(actualProfile.databaseName, tabProfile.databaseName); + assert.equal(actualProfile.authenticationType, tabProfile.authenticationType); + assert.equal(actualProfile.password, tabProfile.password); + assert.equal(actualProfile.serverName, tabProfile.serverName); + assert.equal(actualProfile.userName, tabProfile.userName); + }); + + test('getCurrentGlobalConnection returns the connection from OE if there is no active tab, even if OE is not focused', () => { + let connectionProfile = { databaseName: 'test_database', id: 'test_id', authenticationType: 'SQL Login', password: 'test_password', serverName: 'test_server', userName: 'test_user' } as IConnectionProfile; + let mockObjectExplorerService = TypeMoq.Mock.ofInstance({isFocused: () => undefined, getSelectedProfileAndDatabase: () => undefined } as IObjectExplorerService); + let mockConnectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService); + let mockWorkbenchEditorService = TypeMoq.Mock.ofType(WorkbenchEditorTestService); + let oeProfile = new ConnectionProfile(undefined, connectionProfile); + mockObjectExplorerService.setup(x => x.isFocused()).returns(() => false); + mockObjectExplorerService.setup(x => x.getSelectedProfileAndDatabase()).returns(() => { + return { profile: oeProfile, databaseName: undefined }; + }); + mockConnectionManagementService.setup(x => x.isProfileConnected(TypeMoq.It.is(profile => profile === oeProfile))).returns(() => true); + mockWorkbenchEditorService.setup(x => x.getActiveEditorInput()).returns(() => undefined); + + // If I call getCurrentGlobalConnection, it should return the expected profile from OE + let actualProfile = TaskUtilities.getCurrentGlobalConnection(mockObjectExplorerService.object, mockConnectionManagementService.object, mockWorkbenchEditorService.object); + assert.equal(actualProfile, oeProfile); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 650c5a9c48..da9fd42493 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -50,6 +50,10 @@ import { Color } from 'vs/base/common/color'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import * as QueryConstants from 'sql/parts/query/common/constants'; import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; +import * as TaskUtilities from 'sql/workbench/common/taskUtilities'; +import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; +import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; +import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService'; interface IEditorInputLabel { name: string; @@ -85,7 +89,11 @@ export class TabsTitleControl extends TitleControl { @IFileService private fileService: IFileService, @IWorkspacesService private workspacesService: IWorkspacesService, // {{SQL CARBON EDIT}} -- Display the editor's tab color - @IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService + @IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService, + @IConnectionManagementService private connectionService: IConnectionManagementService, + @IQueryEditorService private queryEditorService: IQueryEditorService, + @IObjectExplorerService private objectExplorerService: IObjectExplorerService, + @IWorkbenchEditorService private workbenchEditorService: IWorkbenchEditorService, ) { super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService, themeService); @@ -154,7 +162,7 @@ export class TabsTitleControl extends TitleControl { const group = this.context; if (group) { - this.editorService.openEditor({ options: { pinned: true, index: group.count /* always at the end */ } } as IUntitledResourceInput).done(null, errors.onUnexpectedError); // untitled are always pinned + TaskUtilities.newQuery(undefined, this.connectionService, this.queryEditorService, this.objectExplorerService, this.workbenchEditorService).then(undefined, errors.onUnexpectedError); } } })); diff --git a/src/vs/workbench/parts/files/browser/fileActions.ts b/src/vs/workbench/parts/files/browser/fileActions.ts index ce03aa20d2..49812c2361 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -52,6 +52,9 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { once } from 'vs/base/common/event'; +import * as TaskUtilities from 'sql/workbench/common/taskUtilities'; +import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; +import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService'; export interface IEditableData { action: IAction; @@ -533,14 +536,17 @@ export class GlobalNewUntitledFileAction extends Action { // {{SQL CARBON EDIT}} @IUntitledEditorService private untitledEditorService: IUntitledEditorService, // {{SQL CARBON EDIT}} - @IQueryEditorService private queryEditorService: IQueryEditorService + @IQueryEditorService private queryEditorService: IQueryEditorService, + @IConnectionManagementService private connectionManagementService: IConnectionManagementService, + @IObjectExplorerService protected _objectExplorerService: IObjectExplorerService, + @IWorkbenchEditorService protected _workbenchEditorService: IWorkbenchEditorService ) { super(id, label); } public run(): TPromise { - // {{SQL CARBON EDIT}} - this.queryEditorService.newSqlEditor(undefined, undefined); + // {{SQL CARBON EDIT}} + TaskUtilities.newQuery(undefined, this.connectionManagementService, this.queryEditorService, this._objectExplorerService, this._workbenchEditorService); return undefined; } }