From d4ffe53dbd46829c77243e52cf06a9d6bc782f43 Mon Sep 17 00:00:00 2001 From: Chris LaFreniere <40371649+chlafreniere@users.noreply.github.com> Date: Fri, 15 Feb 2019 16:50:14 -1000 Subject: [PATCH] Add database name to attach to (if not connected to master) (#4076) --- src/sql/parts/notebook/notebookActions.ts | 38 ++++++-- src/sql/parts/notebook/notebookUtils.ts | 21 +++++ .../notebook/model/notebookUtils.test.ts | 88 +++++++++++++++++++ 3 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 src/sqltest/parts/notebook/model/notebookUtils.test.ts diff --git a/src/sql/parts/notebook/notebookActions.ts b/src/sql/parts/notebook/notebookActions.ts index 38541e72b7..bdcf097797 100644 --- a/src/sql/parts/notebook/notebookActions.ts +++ b/src/sql/parts/notebook/notebookActions.ts @@ -12,10 +12,10 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview import { INotificationService, Severity, INotificationActions } from 'vs/platform/notification/common/notification'; import { SelectBox, ISelectBoxOptionsWithLabel } from 'sql/base/browser/ui/selectBox/selectBox'; -import { INotebookModel } from 'sql/parts/notebook/models/modelInterfaces'; +import { INotebookModel, IDefaultConnection } from 'sql/parts/notebook/models/modelInterfaces'; import { CellType } from 'sql/parts/notebook/models/contracts'; import { NotebookComponent } from 'sql/parts/notebook/notebook.component'; -import { getErrorMessage } from 'sql/parts/notebook/notebookUtils'; +import { getErrorMessage, formatServerNameWithDatabaseNameForAttachTo, getServerFromFormattedAttachToName, getDatabaseFromFormattedAttachToName } from 'sql/parts/notebook/notebookUtils'; import { IConnectionManagementService, IConnectionDialogService } from 'sql/platform/connection/common/connectionManagement'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; @@ -299,8 +299,7 @@ export class AttachToDropdown extends SelectBox { }); } this.onDidSelect(e => { - let connection = this.model.contexts.otherConnections.find((c) => c.serverName === e.selected); - this.doChangeContext(new ConnectionProfile(this._capabilitiesService, connection)); + this.doChangeContext(new ConnectionProfile(this._capabilitiesService, this.getConnectionWithServerAndDatabaseNames(e.selected))); }); } @@ -375,15 +374,24 @@ export class AttachToDropdown extends SelectBox { public getConnections(model: INotebookModel): string[] { let otherConnections: ConnectionProfile[] = []; model.contexts.otherConnections.forEach((conn) => { otherConnections.push(conn); }); - this.selectWithOptionName(model.contexts.defaultConnection.serverName); + // If current connection connects to master, select the option in the dropdown that doesn't specify a database + if (!model.contexts.defaultConnection.databaseName) { + this.selectWithOptionName(model.contexts.defaultConnection.serverName); + } else { + if (model.contexts.defaultConnection) { + this.selectWithOptionName(formatServerNameWithDatabaseNameForAttachTo(model.contexts.defaultConnection)); + } else { + this.select(0); + } + } otherConnections = this.setConnectionsList(model.contexts.defaultConnection, model.contexts.otherConnections); - let connections = otherConnections.map((context) => context.serverName); + let connections = otherConnections.map((context) => context.databaseName ? context.serverName + ' (' + context.databaseName + ')' : context.serverName); return connections; } private setConnectionsList(defaultConnection: ConnectionProfile, otherConnections: ConnectionProfile[]) { if (defaultConnection.serverName !== msgSelectConnection) { - otherConnections = otherConnections.filter(conn => conn.serverName !== defaultConnection.serverName); + otherConnections = otherConnections.filter(conn => conn.id !== defaultConnection.id); otherConnections.unshift(defaultConnection); if (otherConnections.length > 1) { otherConnections = otherConnections.filter(val => val.serverName !== msgSelectConnection); @@ -392,6 +400,20 @@ export class AttachToDropdown extends SelectBox { return otherConnections; } + public getConnectionWithServerAndDatabaseNames(selection: string): ConnectionProfile { + // Find all connections with the the same server as the selected option + let connections = this.model.contexts.otherConnections.filter((c) => selection === c.serverName); + // If only one connection exists with the same server name, use that one + if (connections.length === 1) { + return connections[0]; + } else { + // Extract server and database name + let serverName = getServerFromFormattedAttachToName(selection); + let databaseName = getDatabaseFromFormattedAttachToName(selection); + return this.model.contexts.otherConnections.find((c) => serverName === c.serverName && databaseName === c.databaseName); + } + } + public doChangeContext(connection?: ConnectionProfile, hideErrorMessage?: boolean): void { if (this.value === msgAddNewConnection) { this.openConnectionDialog(); @@ -416,7 +438,7 @@ export class AttachToDropdown extends SelectBox { return; } let connectionProfile = new ConnectionProfile(this._capabilitiesService, connection); - let connectedServer = connectionProfile.serverName; + let connectedServer = formatServerNameWithDatabaseNameForAttachTo(connectionProfile); //Check to see if the same server is already there in dropdown. We only have server names in dropdown if (attachToConnections.some(val => val === connectedServer)) { this.loadAttachToDropdown(this.model, this.getKernelDisplayName()); diff --git a/src/sql/parts/notebook/notebookUtils.ts b/src/sql/parts/notebook/notebookUtils.ts index 1f7dab4f8e..5c15fb52fb 100644 --- a/src/sql/parts/notebook/notebookUtils.ts +++ b/src/sql/parts/notebook/notebookUtils.ts @@ -13,6 +13,7 @@ import { localize } from 'vs/nls'; import { IOutputChannel } from 'vs/workbench/parts/output/common/output'; import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE, INotebookService } from 'sql/workbench/services/notebook/common/notebookService'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; /** @@ -79,6 +80,26 @@ export function sqlNotebooksEnabled(contextKeyService: IContextKeyService) { return contextKeyService.contextMatchesRules(ContextKeyExpr.equals('config.notebook.sqlKernelEnabled', true)); } +// In the Attach To dropdown, show the database name (if it exists) using the current connection +// Example: myFakeServer (myDatabase) +export function formatServerNameWithDatabaseNameForAttachTo(connectionProfile: ConnectionProfile): string { + if (connectionProfile && connectionProfile.serverName) { + return !connectionProfile.databaseName ? connectionProfile.serverName : connectionProfile.serverName + ' (' + connectionProfile.databaseName + ')'; + } + return ''; +} + +// Extract server name from format used in Attach To: serverName (databaseName) +export function getServerFromFormattedAttachToName(name: string): string { + return name.substring(0, name.lastIndexOf(' (')) ? name.substring(0, name.lastIndexOf(' (')) : name; +} + +// Extract database name from format used in Attach To: serverName (databaseName) +export function getDatabaseFromFormattedAttachToName(name: string): string { + return name.substring(name.lastIndexOf('(') + 1, name.lastIndexOf(')')) ? + name.substring(name.lastIndexOf('(') + 1, name.lastIndexOf(')')) : ''; +} + export interface IStandardKernelWithProvider { readonly name: string; readonly connectionProviderIds: string[]; diff --git a/src/sqltest/parts/notebook/model/notebookUtils.test.ts b/src/sqltest/parts/notebook/model/notebookUtils.test.ts new file mode 100644 index 0000000000..ea56e00afa --- /dev/null +++ b/src/sqltest/parts/notebook/model/notebookUtils.test.ts @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as should from 'should'; +import { nb, IConnectionProfile } from 'sqlops'; + +import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService'; +import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; +import { formatServerNameWithDatabaseNameForAttachTo, getServerFromFormattedAttachToName, getDatabaseFromFormattedAttachToName } from 'sql/parts/notebook/notebookUtils'; + +describe('notebookUtils', function(): void { + let conn: IConnectionProfile = { + connectionName: '', + serverName: '', + databaseName: '', + userName: '', + password: '', + authenticationType: '', + savePassword: true, + groupFullName: '', + groupId: '', + providerName: '', + saveProfile: true, + id: '', + options: {}, + azureTenantId: undefined + }; + + it('Should format server and database name correctly for attach to', async function(): Promise { + let capabilitiesService = new CapabilitiesTestService(); + let connProfile = new ConnectionProfile(capabilitiesService, conn); + connProfile.serverName = 'serverName'; + connProfile.databaseName = 'databaseName'; + let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); + should(attachToNameFormatted).equal('serverName (databaseName)'); + }); + + it('Should format server name correctly for attach to', async function(): Promise { + let capabilitiesService = new CapabilitiesTestService(); + let connProfile = new ConnectionProfile(capabilitiesService, conn); + connProfile.serverName = 'serverName'; + let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); + should(attachToNameFormatted).equal('serverName'); + }); + + it('Should format server name correctly for attach to when database is undefined', async function(): Promise { + let capabilitiesService = new CapabilitiesTestService(); + let connProfile = new ConnectionProfile(capabilitiesService, conn); + connProfile.serverName = 'serverName'; + connProfile.databaseName = undefined; + let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); + should(attachToNameFormatted).equal('serverName'); + }); + + it('Should format server name as empty string when server/database are undefined', async function(): Promise { + let capabilitiesService = new CapabilitiesTestService(); + let connProfile = new ConnectionProfile(capabilitiesService, conn); + connProfile.serverName = undefined; + connProfile.databaseName = undefined; + let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); + should(attachToNameFormatted).equal(''); + }); + + it('Should extract server name when no database specified', async function(): Promise { + let serverName = getServerFromFormattedAttachToName('serverName'); + let databaseName = getDatabaseFromFormattedAttachToName('serverName'); + should(serverName).equal('serverName'); + should(databaseName).equal(''); + }); + + it('Should extract server and database name', async function(): Promise { + let serverName = getServerFromFormattedAttachToName('serverName (databaseName)'); + let databaseName = getDatabaseFromFormattedAttachToName('serverName (databaseName)'); + should(serverName).equal('serverName'); + should(databaseName).equal('databaseName'); + }); + + it('Should extract server and database name with other parentheses', async function(): Promise { + let serverName = getServerFromFormattedAttachToName('serv()erName (databaseName)'); + let databaseName = getDatabaseFromFormattedAttachToName('serv()erName (databaseName)'); + should(serverName).equal('serv()erName'); + should(databaseName).equal('databaseName'); + }); +});