SQL DB Project Extension - Providing publish deploy options (#17993)

* initilaizing the sqlproj display options work

* added more code to the options dialog, need default option values

* Except REset Button, all works and need to refactor and test

* DisplayOptions for SQL DB Proj completed, refactor needed

* Tests for display options

* refactor code and removed unnecessary await calls

* Description values getting from STS API and code refactor

* DacpacExtesnion test model error fix

* updates related to STS model updates

* Dac Deployement options model updates according to STS changes

* Undoing file updates and moving as separate PR

* Undoing vscode whitespace changes which were added accidentally

* Options display name coming from API

* Updated model with dac deploy options display names coming from the STS API

* errors fixed after merge to main

* separating model related changes after merge, separate PR exists

* wrong Comment for the test case removed

* code refactor updates

* Hyperlink changes to secondary button and style updates

* refactoring code and comments

* importing type only instead of whole azdata and comment updates

* projectcontroller test fix

* removed duplicate file updates from other PR

* code refactor according to comments

* Added dispose to the onclick handler

* Added test case

* parameter change results error and fixed

* Addressing all comments with updates

* Unused variable removed and missed with previous commit

* Updated the ppublish button and width

* Adjusted the database dropdown and options margins to make all input align in same line

* Updated the database width and moved stylings to uiconstants file
This commit is contained in:
Sai Avishkar Sreerama
2022-06-06 16:53:53 -05:00
committed by GitHub
parent 0b89272739
commit e08a87d035
8 changed files with 569 additions and 11 deletions

View File

@@ -18,6 +18,7 @@ import { getAgreementDisplayText, getConnectionName, getDockerBaseImages, getPub
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
import { ILocalDbDeployProfile } from '../models/deploy/deployProfile';
import { Deferred } from '../common/promise';
import { PublishOptionsDialog } from './publishOptionsDialog';
interface DataSourceDropdownValue extends azdataType.CategoryValue {
dataSource: SqlConnectionDataSource;
@@ -56,6 +57,8 @@ export class PublishDatabaseDialog {
private deploymentOptions: DeploymentOptions | undefined;
private profileUsed: boolean = false;
private serverName: string | undefined;
protected optionsButton: azdataType.ButtonComponent | undefined;
private publishOptionsDialog: PublishOptionsDialog | undefined;
private completionPromise: Deferred = new Deferred();
@@ -134,13 +137,17 @@ export class PublishDatabaseDialog {
title: constants.sqlCmdVariables
};
// Get the default deployment option and set
const options = await this.getDefaultDeploymentOptions();
this.setDeploymentOptions(options);
const profileRow = this.createProfileRow(view);
this.connectionRow = this.createConnectionRow(view);
this.databaseRow = this.createDatabaseRow(view);
const displayOptionsButton = this.createOptionsButton(view);
const horizontalFormSection = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
horizontalFormSection.addItems([profileRow, this.databaseRow]);
horizontalFormSection.addItems([profileRow, this.databaseRow, displayOptionsButton]);
this.formBuilder = <azdataType.FormBuilder>view.modelBuilder.formContainer()
.withFormItems([
@@ -281,8 +288,6 @@ export class PublishDatabaseDialog {
}
public async getDeploymentOptions(): Promise<DeploymentOptions> {
// eventually, database options will be configurable in this dialog
// but for now, just send the default DacFx deployment options if no options were loaded from a publish profile
if (!this.deploymentOptions) {
// We only use the dialog in ADS context currently so safe to cast to the mssql DeploymentOptions here
this.deploymentOptions = await utils.getDefaultPublishDeploymentOptions(this.project) as DeploymentOptions;
@@ -534,7 +539,7 @@ export class PublishDatabaseDialog {
width: cssStyles.publishDialogLabelWidth
}).component();
const connectionRow = view.modelBuilder.flexContainer().withItems([serverLabel, 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': '-8px 10px -15px 0' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
connectionRow.insertItem(selectConnectionButton, 2, { CSSStyles: { 'margin-right': '0px' } });
return connectionRow;
@@ -650,7 +655,7 @@ export class PublishDatabaseDialog {
this.targetDatabaseTextBox = view.modelBuilder.inputBox().withProps({
ariaLabel: constants.databaseNameLabel,
required: true,
width: cssStyles.publishDialogTextboxWidth,
width: cssStyles.publishDialogDropdownWidth,
value: this.getDefaultDatabaseName()
}).component();
}
@@ -662,7 +667,7 @@ export class PublishDatabaseDialog {
value: this.getDefaultDatabaseName(),
ariaLabel: constants.databaseNameLabel,
required: true,
width: cssStyles.publishDialogTextboxWidth,
width: cssStyles.publishDialogDropdownWidth,
editable: true,
fireOnTextChange: true
}).component();
@@ -896,6 +901,42 @@ export class PublishDatabaseDialog {
return true;
}
/*
* Creates Display options container with a 'configure options' button
*/
private createOptionsButton(view: azdataType.ModelView): azdataType.FlexContainer {
this.optionsButton = view.modelBuilder.button().withProps({
label: constants.publishingOptions,
secondary: true,
width: cssStyles.PublishingOptionsButtonWidth
}).component();
const optionsRow = view.modelBuilder.flexContainer().withItems([this.optionsButton], { CSSStyles: { flex: '0 0 auto', 'margin': '6px 0 0 287px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
this.toDispose.push(this.optionsButton.onDidClick(async () => {
TelemetryReporter.sendActionEvent(TelemetryViews.SqlProjectPublishDialog, TelemetryActions.publishConfigureOptionsClicked);
// Create fresh options dialog with default selections each time when creating the 'configure options' button
this.publishOptionsDialog = new PublishOptionsDialog(this.deploymentOptions!, this);
this.publishOptionsDialog.openDialog();
}));
return optionsRow;
}
/*
* Gets the default deployment options from the dacfx service
*/
public async getDefaultDeploymentOptions(): Promise<DeploymentOptions> {
return await utils.getDefaultPublishDeploymentOptions(this.project) as DeploymentOptions;
}
/*
* Sets the default deployment options to deployment options model object
*/
public setDeploymentOptions(deploymentOptions: DeploymentOptions): void {
this.deploymentOptions = deploymentOptions;
}
}
export function promptForPublishProfile(defaultPath: string): Thenable<vscode.Uri[] | undefined> {

View File

@@ -0,0 +1,167 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as constants from '../common/constants';
import * as vscode from 'vscode';
import * as mssql from 'mssql';
import * as utils from '../common/utils';
import type * as azdataType from 'azdata';
import { PublishDatabaseDialog } from './publishDatabaseDialog';
import { DeployOptionsModel } from '../models/options/deployOptionsModel';
export class PublishOptionsDialog {
public dialog!: azdataType.window.Dialog;
private optionsTab: azdataType.window.DialogTab | undefined;
private disposableListeners: vscode.Disposable[] = [];
private descriptionHeading: azdataType.TableComponent | undefined;
private descriptionText: azdataType.TextComponent | undefined;
private optionsTable: azdataType.TableComponent | undefined;
public optionsModel: DeployOptionsModel;
private optionsFlexBuilder: azdataType.FlexContainer | undefined;
constructor(defaultOptions: mssql.DeploymentOptions, private publish: PublishDatabaseDialog) {
this.optionsModel = new DeployOptionsModel(defaultOptions);
}
protected initializeDialog(): void {
this.optionsTab = utils.getAzdataApi()!.window.createTab(constants.publishOptions);
this.intializeDeploymentOptionsDialogTab();
this.dialog.content = [this.optionsTab];
}
public openDialog(): void {
this.dialog = utils.getAzdataApi()!.window.createModelViewDialog(constants.publishOptions);
this.initializeDialog();
this.dialog.okButton.label = constants.okString;
this.dialog.okButton.onClick(() => this.execute());
this.dialog.cancelButton.label = constants.cancelButtonText;
this.dialog.cancelButton.onClick(() => this.cancel());
let resetButton = utils.getAzdataApi()!.window.createButton(constants.ResetButton);
resetButton.onClick(async () => await this.reset());
this.dialog.customButtons = [resetButton];
utils.getAzdataApi()!.window.openDialog(this.dialog);
}
private intializeDeploymentOptionsDialogTab(): void {
this.optionsTab?.registerContent(async view => {
this.descriptionHeading = view.modelBuilder.table().withProps({
data: [],
columns: [
{
value: constants.OptionDescription,
headerCssClass: 'no-borders',
toolTip: constants.OptionDescription
}
]
}).component();
this.descriptionText = view.modelBuilder.text().withProps({
value: ' '
}).component();
this.optionsTable = view.modelBuilder.table().component();
await this.updateOptionsTable();
// Get the description of the selected option
this.disposableListeners.push(this.optionsTable.onRowSelected(async () => {
const row = this.optionsTable?.selectedRows![0];
const label = this.optionsModel.optionsLabels[row!];
await this.descriptionText?.updateProperties({
value: this.optionsModel.getDescription(label)
});
}));
// Update deploy options value on checkbox onchange
this.disposableListeners.push(this.optionsTable.onCellAction!((rowState) => {
const checkboxState = <azdataType.ICheckboxCellActionEventArgs>rowState;
if (checkboxState && checkboxState.row !== undefined) {
const label = this.optionsModel.optionsLabels[checkboxState.row];
this.optionsModel.optionsLookup[label] = checkboxState.checked;
}
}));
this.optionsFlexBuilder = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'column'
}).component();
this.optionsFlexBuilder.addItem(this.optionsTable, { CSSStyles: { 'overflow': 'scroll', 'height': '65vh' } });
this.optionsFlexBuilder.addItem(this.descriptionHeading, { CSSStyles: { 'font-weight': 'bold', 'height': '30px' } });
this.optionsFlexBuilder.addItem(this.descriptionText, { CSSStyles: { 'padding': '4px', 'margin-right': '10px', 'overflow': 'scroll', 'height': '10vh' } });
await view.initializeModel(this.optionsFlexBuilder);
// focus the first option
await this.optionsTable.focus();
});
}
/*
* Update the default options to the options table area
*/
private async updateOptionsTable(): Promise<void> {
const data = this.optionsModel.getOptionsData();
await this.optionsTable?.updateProperties({
data: data,
columns: [
<azdataType.CheckboxColumn>
{
value: constants.OptionInclude,
type: utils.getAzdataApi()!.ColumnType.checkBox,
action: utils.getAzdataApi()!.ActionOnCellCheckboxCheck.customAction,
headerCssClass: 'display-none',
cssClass: 'no-borders align-with-header',
width: 50
},
{
value: constants.OptionName,
headerCssClass: 'display-none',
cssClass: 'no-borders align-with-header',
width: 50
}
],
ariaRowCount: data.length
});
}
/*
* Ok button click, will update the deployment options with selections
*/
protected execute(): void {
this.optionsModel.setDeploymentOptions();
this.publish.setDeploymentOptions(this.optionsModel.deploymentOptions);
this.disposeListeners();
}
/*
* Cancels the deploy options table dialog and its changes will be disposed
*/
protected cancel(): void {
this.disposeListeners();
}
/*
* Reset button click, resets all the options selection
*/
private async reset(): Promise<void> {
const result = await this.publish.getDefaultDeploymentOptions();
this.optionsModel.deploymentOptions = result;
// This will update the Map table with default values
this.optionsModel.InitializeUpdateOptionsMapTable();
await this.updateOptionsTable();
this.optionsFlexBuilder?.removeItem(this.optionsTable!);
this.optionsFlexBuilder?.insertItem(this.optionsTable!, 0, { CSSStyles: { 'overflow': 'scroll', 'height': '65vh' } });
}
private disposeListeners(): void {
this.disposableListeners.forEach(x => x.dispose());
}
}