Add option to save Publish profile in VScode (#23067)

* Publish profile save changes for VSCode

* Fix publish settings

* Fix publish settings

* Address comments

* Address comments

* Address comments

* Address comment
This commit is contained in:
Sakshi Sharma
2023-05-16 10:47:58 -07:00
committed by GitHub
parent d9220c809c
commit 244d56eb12
7 changed files with 199 additions and 29 deletions

View File

@@ -167,6 +167,7 @@ export const selectDatabase = localize('selectDatabase', "Select database");
export const done = localize('done', "Done");
export const nameMustNotBeEmpty = localize('nameMustNotBeEmpty', "Name must not be empty");
export const versionMustNotBeEmpty = localize('versionMustNotBeEmpty', "Version must not be empty");
export const saveProfile = localize('saveProfile', "Would you like to save the settings in a profile (.publish.xml)?");
//#endregion

View File

@@ -25,7 +25,7 @@ import { ImportDataModel } from '../models/api/import';
import { NetCoreTool, DotNetError } from '../tools/netcoreTool';
import { ShellCommandOptions } from '../tools/shellExecutionHelper';
import { BuildHelper } from '../tools/buildHelper';
import { readPublishProfile, savePublishProfile } from '../models/publishProfile/publishProfile';
import { readPublishProfile, promptForSavingProfile, savePublishProfile } from '../models/publishProfile/publishProfile';
import { AddDatabaseReferenceDialog } from '../dialogs/addDatabaseReferenceDialog';
import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings, IProjectReferenceSettings, INugetPackageReferenceSettings } from '../models/IDatabaseReferenceSettings';
import { DatabaseReferenceTreeItem } from '../models/tree/databaseReferencesTreeItem';
@@ -474,6 +474,7 @@ export class ProjectsController {
if (publishTarget === constants.PublishTargetType.docker) {
const publishToDockerSettings = await getPublishToDockerSettings(project);
void promptForSavingProfile(project, publishToDockerSettings); // not awaiting this call, because saving profile should not stop the actual publish workflow
if (!publishToDockerSettings) {
// User cancelled
return;
@@ -482,6 +483,7 @@ export class ProjectsController {
} else if (publishTarget === constants.PublishTargetType.newAzureServer) {
try {
const settings = await launchCreateAzureServerQuickPick(project, this.azureSqlClient);
void promptForSavingProfile(project, settings); // not awaiting this call, because saving profile should not stop the actual publish workflow
if (settings?.deploySettings && settings?.sqlDbSetting) {
await this.publishToNewAzureServer(project, settings);
}
@@ -492,6 +494,7 @@ export class ProjectsController {
} else {
let settings: ISqlProjectPublishSettings | undefined = await getPublishDatabaseSettings(project);
void promptForSavingProfile(project, settings); // not awaiting this call, because saving profile should not stop the actual publish workflow
if (settings) {
// 5. Select action to take
const action = await vscode.window.showQuickPick(
@@ -537,7 +540,7 @@ export class ProjectsController {
const dacFxService = await utils.getDacFxService();
let result: mssql.DacFxResult;
telemetryProps.profileUsed = (settings.profileUsed ?? false).toString();
telemetryProps.profileUsed = (settings.publishProfileUri !== undefined ? true : false).toString();
const currentDate = new Date();
const actionStartTime = currentDate.getTime();
const currentPublishTimeInfo = `${currentDate.toLocaleDateString()} ${constants.at} ${currentDate.toLocaleTimeString()}`;

View File

@@ -20,7 +20,7 @@ import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/t
import { Deferred } from '../common/promise';
import { PublishOptionsDialog } from './publishOptionsDialog';
import { IPublishToDockerSettings, ISqlProjectPublishSettings } from '../models/deploy/publishSettings';
import { PublishProfile } from '../models/publishProfile/publishProfile';
import { PublishProfile, promptToSaveProfile } from '../models/publishProfile/publishProfile';
interface DataSourceDropdownValue extends azdataType.CategoryValue {
dataSource: SqlConnectionDataSource;
@@ -56,7 +56,6 @@ export class PublishDatabaseDialog {
private connectionIsDataSource: boolean | undefined;
private sqlCmdVars: Map<string, string> | undefined;
private deploymentOptions: DeploymentOptions | undefined;
private profileUsed: boolean = false;
private serverName: string | undefined;
protected optionsButton: azdataType.ButtonComponent | undefined;
private publishOptionsDialog: PublishOptionsDialog | undefined;
@@ -240,7 +239,7 @@ export class PublishDatabaseDialog {
connectionUri: await this.getConnectionUri(),
sqlCmdVariables: this.getSqlCmdVariablesForPublish(),
deploymentOptions: await this.getDeploymentOptions(),
profileUsed: this.profileUsed
publishProfileUri: this.publishProfileUri
};
utils.getAzdataApi()!.window.closeDialog(this.dialog);
@@ -273,7 +272,7 @@ export class PublishDatabaseDialog {
connectionUri: '',
sqlCmdVariables: this.getSqlCmdVariablesForPublish(),
deploymentOptions: await this.getDeploymentOptions(),
profileUsed: this.profileUsed
publishProfileUri: this.publishProfileUri
}
};
@@ -294,7 +293,7 @@ export class PublishDatabaseDialog {
connectionUri: await this.getConnectionUri(),
sqlCmdVariables: sqlCmdVars,
deploymentOptions: await this.getDeploymentOptions(),
profileUsed: this.profileUsed
publishProfileUri: this.publishProfileUri
};
utils.getAzdataApi()!.window.closeDialog(this.dialog);
@@ -831,7 +830,6 @@ export class PublishDatabaseDialog {
this.loadProfileTextBox!.value = fileUris[0].fsPath;
await this.loadProfileTextBox!.updateProperty('title', fileUris[0].fsPath);
this.profileUsed = true;
this.publishProfileUri = fileUris[0];
}
});
@@ -850,15 +848,7 @@ export class PublishDatabaseDialog {
}).component();
saveProfileAsButton.onDidClick(async () => {
const filePath = await vscode.window.showSaveDialog(
{
defaultUri: this.publishProfileUri ?? vscode.Uri.file(path.join(this.project.projectFolderPath, `${this.project.projectFileName}_1.publish.xml`)),
saveLabel: constants.save,
filters: {
'Publish Settings Files': ['publish.xml'],
}
}
);
const filePath = await promptToSaveProfile(this.project, this.publishProfileUri);
if (!filePath) {
return;
@@ -873,7 +863,6 @@ export class PublishDatabaseDialog {
TelemetryReporter.sendActionEvent(TelemetryViews.SqlProjectPublishDialog, TelemetryActions.profileSaved);
}
this.profileUsed = true;
this.publishProfileUri = filePath;
await this.project.addNoneItem(path.relative(this.project.projectFolderPath, filePath.fsPath));

View File

@@ -21,6 +21,7 @@ import { ISqlProjectPublishSettings } from '../models/deploy/publishSettings';
export async function getPublishDatabaseSettings(project: ISqlProject, promptForConnection: boolean = true): Promise<ISqlProjectPublishSettings | undefined> {
// 1. Select publish settings file (optional)
let publishProfileUri;
// Create custom quickpick so we can control stuff like displaying the loading indicator
const quickPick = vscode.window.createQuickPick();
quickPick.items = [{ label: constants.dontUseProfile }, { label: constants.browseForProfileWithIcon }];
@@ -42,7 +43,7 @@ export async function getPublishDatabaseSettings(project: ISqlProject, promptFor
// If the user cancels out of the file picker then just return and let them choose another option
return;
}
let publishProfileUri = locations[0];
publishProfileUri = locations[0];
try {
// Show loading state while reading profile
quickPick.busy = true;
@@ -214,7 +215,7 @@ export async function getPublishDatabaseSettings(project: ISqlProject, promptFor
connectionUri: connectionUri || '',
sqlCmdVariables: sqlCmdVariables,
deploymentOptions: publishProfile?.options ?? await getDefaultPublishDeploymentOptions(project),
profileUsed: !!publishProfile
publishProfileUri: publishProfileUri
};
return settings;
}

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { ISqlConnectionProperties } from 'sqldbproj';
import { DeploymentOptions as mssqlDeploymentOptions } from 'mssql';
import { DeploymentOptions as vscodeMssqlDeploymentOptions } from 'vscode-mssql';
@@ -18,7 +19,7 @@ export interface ISqlProjectPublishSettings {
connectionUri: string;
sqlCmdVariables?: Map<string, string>;
deploymentOptions?: DeploymentOptions;
profileUsed?: boolean;
publishProfileUri?: vscode.Uri;
}
/**

View File

@@ -9,10 +9,14 @@ import * as utils from '../../common/utils';
import * as mssql from 'mssql';
import * as vscodeMssql from 'vscode-mssql';
import * as vscode from 'vscode';
import * as path from 'path';
import { promises as fs } from 'fs';
import { SqlConnectionDataSource } from '../dataSources/sqlConnectionStringSource';
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../../common/telemetry';
import { Project } from '../project';
import { IPublishToDockerSettings, ISqlProjectPublishSettings } from '../deploy/publishSettings';
import { ISqlDbDeployProfile } from '../deploy/deployProfile';
// only reading db name, connection string, and SQLCMD vars from profile for now
export interface PublishProfile {
@@ -129,7 +133,181 @@ async function readConnectionString(xmlDoc: any): Promise<{ connectionId: string
/**
* saves publish settings to the specified profile file
*/
export async function savePublishProfile(profilePath: string, databaseName: string, connectionString: string, sqlCommandVariableValues?: Map<string, string>, deploymentOptions?: mssql.DeploymentOptions): Promise<void> {
export async function savePublishProfile(profilePath: string, databaseName: string, connectionString: string, sqlCommandVariableValues?: Map<string, string>, deploymentOptions?: mssql.DeploymentOptions | vscodeMssql.DeploymentOptions): Promise<void> {
const dacFxService = await utils.getDacFxService();
await dacFxService.savePublishProfile(profilePath, databaseName, connectionString, sqlCommandVariableValues, deploymentOptions);
if (utils.getAzdataApi()) {
await (dacFxService as mssql.IDacFxService).savePublishProfile(profilePath, databaseName, connectionString, sqlCommandVariableValues, deploymentOptions as mssql.DeploymentOptions);
} else {
await (dacFxService as vscodeMssql.IDacFxService).savePublishProfile(profilePath, databaseName, connectionString, sqlCommandVariableValues, deploymentOptions as vscodeMssql.DeploymentOptions);
}
}
export function promptToSaveProfile(project: Project, publishProfileUri?: vscode.Uri) {
return vscode.window.showSaveDialog(
{
defaultUri: publishProfileUri ?? vscode.Uri.file(path.join(project.projectFolderPath, `${project.projectFileName}_1.publish.xml`)),
saveLabel: constants.save,
filters: {
'Publish files': ['publish.xml'],
}
}
);
}
/**
* Prompt to save publish profile and add to the tree
* @param project
* @param settings Publish settings
* @returns
*/
export async function promptForSavingProfile(project: Project, settings: ISqlProjectPublishSettings | ISqlDbDeployProfile | IPublishToDockerSettings | undefined) {
const result = await vscode.window.showInformationMessage(constants.saveProfile, constants.yesString, constants.noString);
if (result === constants.yesString) {
let publishProfileUri: vscode.Uri | undefined;
if (settings) {
if (isISqlProjectPublishSettings(settings)) {
publishProfileUri = settings.publishProfileUri;
} else if (isISqlDbDeployProfile(settings)) {
publishProfileUri = settings.deploySettings?.publishProfileUri;
} else if (isIPublishToDockerSettings(settings)) {
publishProfileUri = settings.sqlProjectPublishSettings.publishProfileUri;
}
}
const filePath = await promptToSaveProfile(project, publishProfileUri);
if (!filePath) {
return;
}
const targetConnectionString = await getConnectionString(settings);
const targetDatabaseName = getDatabaseName(settings, project.projectFileName);
const deploymentOptions = await getDeploymentOptions(settings, project);
const sqlCmdVariables = getSqlCmdVariables(settings);
await savePublishProfile(filePath.fsPath, targetDatabaseName, targetConnectionString, sqlCmdVariables, deploymentOptions);
setProfileParameters(settings, filePath);
await project.addNoneItem(path.relative(project.projectFolderPath, filePath.fsPath));
void vscode.commands.executeCommand(constants.refreshDataWorkspaceCommand); //refresh data workspace to load the newly added profile to the tree
}
}
/**
* Function to confirm the Publish to existing server workflow
* @param settings
* @returns true if the settings is of type ISqlProjectPublishSettings
*/
function isISqlProjectPublishSettings(settings: ISqlProjectPublishSettings | ISqlDbDeployProfile | IPublishToDockerSettings | undefined): settings is ISqlProjectPublishSettings {
if ((settings as ISqlProjectPublishSettings).connectionUri) {
return true
}
return false
}
/**
* Function to confirm the Publish to New Azure server workflow
* @param settings
* @returns true if the settings is of type ISqlDbDeployProfile
*/
function isISqlDbDeployProfile(settings: ISqlProjectPublishSettings | ISqlDbDeployProfile | IPublishToDockerSettings | undefined): settings is ISqlDbDeployProfile {
if ((settings as ISqlDbDeployProfile).deploySettings) {
return true
}
return false
}
/**
* Function to confirm the Publish to Docker workflow
* @param settings
* @returns true if the settings is of type IPublishToDockerSettings
*/
function isIPublishToDockerSettings(settings: ISqlProjectPublishSettings | ISqlDbDeployProfile | IPublishToDockerSettings | undefined): settings is IPublishToDockerSettings {
if ((settings as IPublishToDockerSettings).dockerSettings) {
return true
}
return false
}
async function getConnectionString(settings: ISqlProjectPublishSettings | ISqlDbDeployProfile | IPublishToDockerSettings | undefined): Promise<string> {
let connectionUri: string = '';
let connectionString: string = '';
if (settings) {
if (isISqlProjectPublishSettings(settings)) {
connectionUri = settings.connectionUri;
} else if (isISqlDbDeployProfile(settings)) {
connectionUri = settings.deploySettings?.connectionUri ?? '';
} else if (isIPublishToDockerSettings(settings)) {
connectionUri = settings.sqlProjectPublishSettings.connectionUri;
}
}
if (connectionUri) {
connectionString = await (await utils.getVscodeMssqlApi()).getConnectionString(connectionUri, false);
}
return connectionString;
}
function getDatabaseName(settings: ISqlProjectPublishSettings | ISqlDbDeployProfile | IPublishToDockerSettings | undefined, projectName: string): string {
let databaseName: string = projectName;
if (settings) {
if (isISqlProjectPublishSettings(settings)) {
databaseName = settings.databaseName;
} else if (isISqlDbDeployProfile(settings)) {
databaseName = settings.deploySettings?.databaseName ?? '';
} else if (isIPublishToDockerSettings(settings)) {
databaseName = settings.sqlProjectPublishSettings.databaseName;
}
}
return databaseName;
}
async function getDeploymentOptions(settings: ISqlProjectPublishSettings | ISqlDbDeployProfile | IPublishToDockerSettings | undefined, project: Project): Promise<vscodeMssql.DeploymentOptions | undefined> {
let deploymentOptions: vscodeMssql.DeploymentOptions | undefined;
if (settings) {
if (isISqlProjectPublishSettings(settings)) {
deploymentOptions = settings.deploymentOptions;
} else if (isISqlDbDeployProfile(settings)) {
deploymentOptions = settings.deploySettings?.deploymentOptions;
} else if (isIPublishToDockerSettings(settings)) {
deploymentOptions = settings.sqlProjectPublishSettings.deploymentOptions;
}
} else {
deploymentOptions = await utils.getDefaultPublishDeploymentOptions(project);
}
return deploymentOptions;
}
function getSqlCmdVariables(settings: ISqlProjectPublishSettings | ISqlDbDeployProfile | IPublishToDockerSettings | undefined): Map<string, string> | undefined {
let sqlCmdVariables: Map<string, string> | undefined;
if (settings) {
if (isISqlProjectPublishSettings(settings)) {
sqlCmdVariables = settings.sqlCmdVariables;
} else if (isISqlDbDeployProfile(settings)) {
sqlCmdVariables = settings.deploySettings?.sqlCmdVariables;
} else if (isIPublishToDockerSettings(settings)) {
sqlCmdVariables = settings.sqlProjectPublishSettings.sqlCmdVariables;
}
}
return sqlCmdVariables;
}
function setProfileParameters(settings: ISqlProjectPublishSettings | ISqlDbDeployProfile | IPublishToDockerSettings | undefined, profilePath: vscode.Uri) {
if (settings) {
if (isISqlProjectPublishSettings(settings)) {
settings.publishProfileUri = profilePath;
} else if (isISqlDbDeployProfile(settings)) {
if (settings.deploySettings) {
settings.deploySettings.publishProfileUri = profilePath;
}
} else if (isIPublishToDockerSettings(settings)) {
settings.sqlProjectPublishSettings.publishProfileUri = profilePath;
}
}
}

View File

@@ -87,8 +87,7 @@ describe('Publish Database Dialog', () => {
['ProdDatabaseName', 'MyProdDatabase'],
['BackupDatabaseName', 'MyBackupDatabase']
]),
deploymentOptions: mockDacFxOptionsResult.deploymentOptions,
profileUsed: false
deploymentOptions: mockDacFxOptionsResult.deploymentOptions
};
dialog.object.publish = (_, prof) => { profile = prof; };
@@ -104,8 +103,7 @@ describe('Publish Database Dialog', () => {
['ProdDatabaseName', 'MyProdDatabase'],
['BackupDatabaseName', 'MyBackupDatabase']
]),
deploymentOptions: mockDacFxOptionsResult.deploymentOptions,
profileUsed: false
deploymentOptions: mockDacFxOptionsResult.deploymentOptions
};
dialog.object.generateScript = (_, prof) => { profile = prof; };
@@ -132,8 +130,7 @@ describe('Publish Database Dialog', () => {
['ProdDatabaseName', 'MyProdDatabase'],
['BackupDatabaseName', 'MyBackupDatabase']
]),
deploymentOptions: mockDacFxOptionsResult.deploymentOptions,
profileUsed: false
deploymentOptions: mockDacFxOptionsResult.deploymentOptions
}
};
dialog.object.publishToExistingServer = false;