diff --git a/extensions/sql-database-projects/images/connect.svg b/extensions/sql-database-projects/images/connect.svg new file mode 100644 index 0000000000..29e7182774 --- /dev/null +++ b/extensions/sql-database-projects/images/connect.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/sql-database-projects/images/dark/edit.svg b/extensions/sql-database-projects/images/dark/edit.svg deleted file mode 100644 index 345362da0a..0000000000 --- a/extensions/sql-database-projects/images/dark/edit.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/sql-database-projects/images/dark/folder_blue.svg b/extensions/sql-database-projects/images/folder_blue.svg similarity index 100% rename from extensions/sql-database-projects/images/dark/folder_blue.svg rename to extensions/sql-database-projects/images/folder_blue.svg diff --git a/extensions/sql-database-projects/images/light/edit.svg b/extensions/sql-database-projects/images/light/edit.svg deleted file mode 100644 index 345362da0a..0000000000 --- a/extensions/sql-database-projects/images/light/edit.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/sql-database-projects/images/light/folder_blue.svg b/extensions/sql-database-projects/images/light/folder_blue.svg deleted file mode 100644 index 64cbba1769..0000000000 --- a/extensions/sql-database-projects/images/light/folder_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/sql-database-projects/images/light/refresh.svg b/extensions/sql-database-projects/images/light/refresh.svg deleted file mode 100644 index f03579110b..0000000000 --- a/extensions/sql-database-projects/images/light/refresh.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/extensions/sql-database-projects/images/dark/refresh.svg b/extensions/sql-database-projects/images/refresh.svg similarity index 100% rename from extensions/sql-database-projects/images/dark/refresh.svg rename to extensions/sql-database-projects/images/refresh.svg diff --git a/extensions/sql-database-projects/images/selectConnection.svg b/extensions/sql-database-projects/images/selectConnection.svg new file mode 100644 index 0000000000..44d7aa82f9 --- /dev/null +++ b/extensions/sql-database-projects/images/selectConnection.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index fa73bc285f..9e11c2585d 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -87,7 +87,8 @@ export const sqlCmdValueColumn = localize('sqlCmdValueColumn', "Value"); export const loadSqlCmdVarsButtonTitle = localize('reloadValuesFromProjectButtonTitle', "Reload values from project"); export const profile = localize('profile', "Profile"); export const selectConnection = localize('selectConnection', "Select connection"); -export const connection = localize('connection', "Connection"); +export const server = localize('server', "Server"); +export const defaultUser = localize('default', "default"); // Add Database Reference dialog strings diff --git a/extensions/sql-database-projects/src/common/iconHelper.ts b/extensions/sql-database-projects/src/common/iconHelper.ts index 4af64960b3..b92bb4d2ec 100644 --- a/extensions/sql-database-projects/src/common/iconHelper.ts +++ b/extensions/sql-database-projects/src/common/iconHelper.ts @@ -22,7 +22,8 @@ export class IconPathHelper { public static refresh: IconPath; public static folder_blue: IconPath; - public static edit: IconPath; + public static selectConnection: IconPath; + public static connect: IconPath; public static folder: IconPath; @@ -37,19 +38,27 @@ export class IconPathHelper { IconPathHelper.referenceGroup = IconPathHelper.makeIcon('referenceGroup'); IconPathHelper.referenceDatabase = IconPathHelper.makeIcon('reference-database'); - IconPathHelper.refresh = IconPathHelper.makeIcon('refresh'); - IconPathHelper.folder_blue = IconPathHelper.makeIcon('folder_blue'); - IconPathHelper.edit = IconPathHelper.makeIcon('edit'); + IconPathHelper.refresh = IconPathHelper.makeIcon('refresh', true); + IconPathHelper.folder_blue = IconPathHelper.makeIcon('folder_blue', true); + IconPathHelper.selectConnection = IconPathHelper.makeIcon('selectConnection', true); + IconPathHelper.connect = IconPathHelper.makeIcon('connect', true); IconPathHelper.folder = IconPathHelper.makeIcon('folder'); } - private static makeIcon(name: string) { + private static makeIcon(name: string, sameIcon: boolean = false) { const folder = 'images'; - return { - dark: IconPathHelper.extensionContext.asAbsolutePath(`${folder}/dark/${name}.svg`), - light: IconPathHelper.extensionContext.asAbsolutePath(`${folder}/light/${name}.svg`) - }; + if (sameIcon) { + return { + dark: IconPathHelper.extensionContext.asAbsolutePath(`${folder}/${name}.svg`), + light: IconPathHelper.extensionContext.asAbsolutePath(`${folder}/${name}.svg`) + }; + } else { + return { + dark: IconPathHelper.extensionContext.asAbsolutePath(`${folder}/dark/${name}.svg`), + light: IconPathHelper.extensionContext.asAbsolutePath(`${folder}/light/${name}.svg`) + }; + } } } diff --git a/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts b/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts index 6ed2aaae98..a1985129d9 100644 --- a/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts @@ -269,7 +269,8 @@ export class PublishDatabaseDialog { value: '', ariaLabel: constants.targetConnectionLabel, placeHolder: constants.selectConnection, - width: cssStyles.publishDialogTextboxWidth + width: cssStyles.publishDialogTextboxWidth, + enabled: false }).component(); this.targetConnectionTextBox.onTextChanged(() => { @@ -350,13 +351,13 @@ export class PublishDatabaseDialog { this.targetConnectionTextBox = this.createTargetConnectionComponent(view); const selectConnectionButton: azdata.Component = this.createSelectConnectionButton(view); - const connectionLabel = view.modelBuilder.text().withProperties({ - value: constants.connection, + const serverLabel = view.modelBuilder.text().withProperties({ + value: constants.server, requiredIndicator: true, width: cssStyles.publishDialogLabelWidth }).component(); - const connectionRow = view.modelBuilder.flexContainer().withItems([connectionLabel, this.targetConnectionTextBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component(); + const connectionRow = view.modelBuilder.flexContainer().withItems([serverLabel, this.targetConnectionTextBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component(); connectionRow.insertItem(selectConnectionButton, 2, { CSSStyles: { 'margin-right': '0px' } }); return connectionRow; @@ -410,7 +411,7 @@ export class PublishDatabaseDialog { headerCssStyles: cssStyles.tableHeader, rowCssStyles: cssStyles.tableRow }], - width: '410px' + width: '420px' }).component(); table.onDataChanged(() => { @@ -453,7 +454,7 @@ export class PublishDatabaseDialog { private createSelectConnectionButton(view: azdata.ModelView): azdata.Component { let selectConnectionButton: azdata.ButtonComponent = view.modelBuilder.button().withProperties({ ariaLabel: constants.selectConnection, - iconPath: IconPathHelper.edit, + iconPath: IconPathHelper.selectConnection, height: '16px', width: '16px' }).component(); @@ -462,29 +463,47 @@ export class PublishDatabaseDialog { let connection = await azdata.connection.openConnectionDialog(); this.connectionId = connection.connectionId; - // show connection name if there is one, otherwise show connection string + // show connection name if there is one, otherwise show connection in format that shows in OE + let connectionTextboxValue: string; if (connection.options['connectionName']) { - this.targetConnectionTextBox!.value = connection.options['connectionName']; + connectionTextboxValue = connection.options['connectionName']; } else { - this.targetConnectionTextBox!.value = await azdata.connection.getConnectionString(connection.connectionId, false); + let user = connection.options['user']; + if (!user) { + user = constants.defaultUser; + } + + connectionTextboxValue = `${connection.options['server']} (${user})`; } - // populate database dropdown with the databases for this connection - const databaseValues = (await azdata.connection.listDatabases(this.connectionId)) - // filter out system dbs - .filter(db => constants.systemDbs.find(systemdb => db === systemdb) === undefined); - - this.targetDatabaseDropDown!.values = databaseValues; + this.updateConnectionComponents(connectionTextboxValue, this.connectionId); // change the database inputbox value to the connection's database if there is one if (connection.options.database && connection.options.database !== constants.master) { this.targetDatabaseDropDown!.value = connection.options.database; } + + // change icon to the one without a plus sign + selectConnectionButton.iconPath = IconPathHelper.connect; }); return selectConnectionButton; } + private async updateConnectionComponents(connectionTextboxValue: string, connectionId: string) { + this.targetConnectionTextBox!.value = connectionTextboxValue; + this.targetConnectionTextBox!.placeHolder = connectionTextboxValue; + + // populate database dropdown with the databases for this connection + if (connectionId) { + const databaseValues = (await azdata.connection.listDatabases(connectionId)) + // filter out system dbs + .filter(db => constants.systemDbs.find(systemdb => db === systemdb) === undefined); + + this.targetDatabaseDropDown!.values = databaseValues; + } + } + private createLoadProfileButton(view: azdata.ModelView): azdata.ButtonComponent { let loadProfileButton: azdata.ButtonComponent = view.modelBuilder.button().withProperties({ ariaLabel: constants.loadProfilePlaceholderText, @@ -512,10 +531,12 @@ export class PublishDatabaseDialog { if (this.readPublishProfile) { const result = await this.readPublishProfile(fileUris[0]); + // clear out old database dropdown values. They'll get populated later if there was a connection specified in the profile + (this.targetDatabaseDropDown).values = []; (this.targetDatabaseDropDown).value = result.databaseName; this.connectionId = result.connectionId; - (this.targetConnectionTextBox).value = result.connectionString; + await this.updateConnectionComponents(result.connection, this.connectionId); for (let key in result.sqlCmdVariables) { (>this.sqlCmdVars)[key] = result.sqlCmdVariables[key]; @@ -538,8 +559,9 @@ export class PublishDatabaseDialog { this.formBuilder?.removeFormItem(this.sqlCmdVariablesFormComponentGroup); } - // show file path in text box + // show file path in text box and hover text this.loadProfileTextBox!.value = fileUris[0].fsPath; + this.loadProfileTextBox!.placeHolder = fileUris[0].fsPath; } }); diff --git a/extensions/sql-database-projects/src/models/publishProfile/publishProfile.ts b/extensions/sql-database-projects/src/models/publishProfile/publishProfile.ts index 44d3b8cc8a..a758fe84bb 100644 --- a/extensions/sql-database-projects/src/models/publishProfile/publishProfile.ts +++ b/extensions/sql-database-projects/src/models/publishProfile/publishProfile.ts @@ -17,7 +17,7 @@ import { SqlConnectionDataSource } from '../dataSources/sqlConnectionStringSourc export interface PublishProfile { databaseName: string; connectionId: string; - connectionString: string; + connection: string; sqlCmdVariables: Record; options?: mssql.DeploymentOptions; } @@ -46,38 +46,46 @@ export async function load(profileUri: Uri, dacfxService: mssql.IDacFxService): return { databaseName: targetDbName, connectionId: connectionInfo.connectionId, - connectionString: connectionInfo.connectionString, + connection: connectionInfo.connection, sqlCmdVariables: sqlCmdVariables, options: optionsResult.deploymentOptions }; } -async function readConnectionString(xmlDoc: any): Promise<{ connectionId: string, connectionString: string }> { - let targetConnectionString: string = ''; +async function readConnectionString(xmlDoc: any): Promise<{ connectionId: string, connection: string }> { + let targetConnection: string = ''; let connId: string = ''; if (xmlDoc.documentElement.getElementsByTagName(constants.targetConnectionString).length > 0) { - targetConnectionString = xmlDoc.documentElement.getElementsByTagName(constants.TargetConnectionString)[0].textContent; + const targetConnectionString = xmlDoc.documentElement.getElementsByTagName(constants.TargetConnectionString)[0].textContent; const dataSource = new SqlConnectionDataSource('temp', targetConnectionString); + let server: string = ''; + let username: string = ''; const connectionProfile = dataSource.getConnectionProfile(); try { if (dataSource.integratedSecurity) { - connId = (await azdata.connection.connect(connectionProfile, false, false)).connectionId; + const connection = await azdata.connection.connect(connectionProfile, false, false); + connId = connection.connectionId; + server = dataSource.server; + username = constants.defaultUser; } else { - connId = (await azdata.connection.openConnectionDialog(undefined, connectionProfile)).connectionId; + const connection = await azdata.connection.openConnectionDialog(undefined, connectionProfile); + connId = connection.connectionId; + server = connection.options['server']; + username = connection.options['username']; } + + targetConnection = `${server} (${username})`; } catch (err) { throw new Error(constants.unableToCreatePublishConnection(utils.getErrorMessage(err))); } } - // mask password in connection string - targetConnectionString = await azdata.connection.getConnectionString(connId, false); return { connectionId: connId, - connectionString: targetConnectionString + connection: targetConnection }; } diff --git a/extensions/sql-database-projects/src/test/publishProfile.test.ts b/extensions/sql-database-projects/src/test/publishProfile.test.ts index adcfbc6a9a..daf0a7d000 100644 --- a/extensions/sql-database-projects/src/test/publishProfile.test.ts +++ b/extensions/sql-database-projects/src/test/publishProfile.test.ts @@ -43,16 +43,14 @@ describe('Publish profile tests', function (): void { testContext.dacFxService.setup(x => x.getOptionsFromProfile(TypeMoq.It.isAny())).returns(async () => { return Promise.resolve(mockDacFxOptionsResult); }); - const connectionString = 'Data Source=testserver;Integrated Security=true;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True'; sinon.stub(azdata.connection, 'connect').resolves(connectionResult); - sinon.stub(azdata.connection, 'getConnectionString').resolves(connectionString); let result = await load(vscode.Uri.file(profilePath), testContext.dacFxService.object); should(result.databaseName).equal('targetDb'); should(Object.keys(result.sqlCmdVariables).length).equal(1); should(result.sqlCmdVariables['ProdDatabaseName']).equal('MyProdDatabase'); should(result.connectionId).equal('connId'); - should(result.connectionString).equal('Data Source=testserver;Integrated Security=true;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True'); + should(result.connection).equal('testserver (default)'); should(result.options).equal(mockDacFxOptionsResult.deploymentOptions); }); @@ -62,21 +60,22 @@ describe('Publish profile tests', function (): void { const connectionResult = { providerName: 'MSSQL', connectionId: 'connId', - options: {} + options: { + 'server': 'testserver', + 'username': 'testUser' + } }; testContext.dacFxService.setup(x => x.getOptionsFromProfile(TypeMoq.It.isAny())).returns(async () => { return Promise.resolve(mockDacFxOptionsResult); }); - const connectionString = 'Data Source=testserver;User Id=testUser;Password=******;Integrated Security=false;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True'; sinon.stub(azdata.connection, 'openConnectionDialog').resolves(connectionResult); - sinon.stub(azdata.connection, 'getConnectionString').resolves(connectionString); let result = await load(vscode.Uri.file(profilePath), testContext.dacFxService.object); should(result.databaseName).equal('targetDb'); should(Object.keys(result.sqlCmdVariables).length).equal(1); should(result.sqlCmdVariables['ProdDatabaseName']).equal('MyProdDatabase'); should(result.connectionId).equal('connId'); - should(result.connectionString).equal('Data Source=testserver;User Id=testUser;Password=******;Integrated Security=false;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True'); + should(result.connection).equal('testserver (testUser)'); should(result.options).equal(mockDacFxOptionsResult.deploymentOptions); }); @@ -85,9 +84,7 @@ describe('Publish profile tests', function (): void { let profilePath = await testUtils.createTestFile(baselines.publishProfileIntegratedSecurityBaseline, 'publishProfile.publish.xml'); const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider()); - const connectionString = 'Data Source=testserver;Integrated Security=true;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True'; sinon.stub(azdata.connection, 'connect').throws(new Error('Could not connect')); - sinon.stub(azdata.connection, 'getConnectionString').resolves(connectionString); await testUtils.shouldThrowSpecificError(async () => await projController.readPublishProfileCallback(vscode.Uri.file(profilePath)), constants.unableToCreatePublishConnection('Could not connect')); });