Update Schema Compare dialog to start a new connection (#15193)

* Update SC dialog to start a new connection

* Functionally complete

* Fix target db to pick correct database

* Address comments

* Added test+fixed one missing scenario

* Address comments + add one more test
This commit is contained in:
Sakshi Sharma
2021-04-29 09:53:38 -07:00
committed by GitHub
parent e42da81005
commit e695a01538
9 changed files with 241 additions and 62 deletions

View File

@@ -6,6 +6,7 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as loc from '../localizedConstants';
import * as path from 'path';
import { SchemaCompareMainWindow } from '../schemaCompareMainWindow';
import { TelemetryReporter, TelemetryViews } from '../telemetry';
import { getEndpointName, getRootPath, exists } from '../utils';
@@ -27,18 +28,18 @@ export class SchemaCompareDialog {
private sourceTextBox: azdata.InputBoxComponent;
private sourceFileButton: azdata.ButtonComponent;
private sourceServerComponent: azdata.FormComponent;
private sourceServerDropdown: azdata.DropDownComponent;
protected sourceServerDropdown: azdata.DropDownComponent;
private sourceConnectionButton: azdata.ButtonComponent;
private sourceDatabaseComponent: azdata.FormComponent;
private sourceDatabaseDropdown: azdata.DropDownComponent;
private sourceNoActiveConnectionsText: azdata.FormComponent;
private targetDacpacComponent: azdata.FormComponent;
private targetTextBox: azdata.InputBoxComponent;
private targetFileButton: azdata.ButtonComponent;
private targetServerComponent: azdata.FormComponent;
private targetServerDropdown: azdata.DropDownComponent;
protected targetServerDropdown: azdata.DropDownComponent;
private targetConnectionButton: azdata.ButtonComponent;
private targetDatabaseComponent: azdata.FormComponent;
private targetDatabaseDropdown: azdata.DropDownComponent;
private targetNoActiveConnectionsText: azdata.FormComponent;
private formBuilder: azdata.FormBuilder;
private sourceIsDacpac: boolean;
private targetIsDacpac: boolean;
@@ -50,7 +51,12 @@ export class SchemaCompareDialog {
private initDialogComplete: Deferred<void>;
private initDialogPromise: Promise<void> = new Promise<void>((resolve, reject) => this.initDialogComplete = { resolve, reject });
constructor(private schemaCompareMainWindow: SchemaCompareMainWindow, private view?: azdata.ModelView) {
private textBoxWidth: number = 280;
public promise;
public promise2;
constructor(private schemaCompareMainWindow: SchemaCompareMainWindow, private view?: azdata.ModelView, private extensionContext?: vscode.ExtensionContext) {
this.previousSource = schemaCompareMainWindow.sourceEndpointInfo;
this.previousTarget = schemaCompareMainWindow.targetEndpointInfo;
}
@@ -182,7 +188,7 @@ export class SchemaCompareDialog {
this.sourceTextBox = this.view.modelBuilder.inputBox().withProperties({
value: this.schemaCompareMainWindow.sourceEndpointInfo ? this.schemaCompareMainWindow.sourceEndpointInfo.packageFilePath : '',
width: 275,
width: this.textBoxWidth,
ariaLabel: loc.sourceFile
}).component();
@@ -192,7 +198,7 @@ export class SchemaCompareDialog {
this.targetTextBox = this.view.modelBuilder.inputBox().withProperties({
value: this.schemaCompareMainWindow.targetEndpointInfo ? this.schemaCompareMainWindow.targetEndpointInfo.packageFilePath : '',
width: 275,
width: this.textBoxWidth,
ariaLabel: loc.targetFile
}).component();
@@ -214,9 +220,6 @@ export class SchemaCompareDialog {
let sourceRadioButtons = this.createSourceRadiobuttons();
let targetRadioButtons = this.createTargetRadiobuttons();
this.sourceNoActiveConnectionsText = this.createNoActiveConnectionsText();
this.targetNoActiveConnectionsText = this.createNoActiveConnectionsText();
let sourceComponents = [];
let targetComponents = [];
@@ -345,23 +348,20 @@ export class SchemaCompareDialog {
// show dacpac file browser
this.sourceDacpacRadioButton.onDidClick(async () => {
this.sourceIsDacpac = true;
this.formBuilder.removeFormItem(this.sourceNoActiveConnectionsText);
this.formBuilder.removeFormItem(this.sourceServerComponent);
this.formBuilder.removeFormItem(this.sourceDatabaseComponent);
this.formBuilder.insertFormItem(this.sourceDacpacComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// show server and db dropdowns or 'No active connections' text
// show server and db dropdowns
this.sourceDatabaseRadioButton.onDidClick(async () => {
this.sourceIsDacpac = false;
if ((this.sourceServerDropdown.value as ConnectionDropdownValue)) {
this.formBuilder.insertFormItem(this.sourceServerComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
this.formBuilder.insertFormItem(this.sourceDatabaseComponent, 3, { horizontal: true, titleFontSize: titleFontSize });
} else {
this.formBuilder.insertFormItem(this.sourceNoActiveConnectionsText, 2, { horizontal: true, titleFontSize: titleFontSize });
}
this.formBuilder.insertFormItem(this.sourceServerComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
this.formBuilder.insertFormItem(this.sourceDatabaseComponent, 3, { horizontal: true, titleFontSize: titleFontSize });
this.formBuilder.removeFormItem(this.sourceDacpacComponent);
this.populateServerDropdown(false);
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
@@ -401,23 +401,20 @@ export class SchemaCompareDialog {
// show dacpac file browser
dacpacRadioButton.onDidClick(async () => {
this.targetIsDacpac = true;
this.formBuilder.removeFormItem(this.targetNoActiveConnectionsText);
this.formBuilder.removeFormItem(this.targetServerComponent);
this.formBuilder.removeFormItem(this.targetDatabaseComponent);
this.formBuilder.addFormItem(this.targetDacpacComponent, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// show server and db dropdowns or 'No active connections' text
// show server and db dropdowns
databaseRadioButton.onDidClick(async () => {
this.targetIsDacpac = false;
this.formBuilder.removeFormItem(this.targetDacpacComponent);
if ((this.targetServerDropdown.value as ConnectionDropdownValue)) {
this.formBuilder.addFormItem(this.targetServerComponent, { horizontal: true, titleFontSize: titleFontSize });
this.formBuilder.addFormItem(this.targetDatabaseComponent, { horizontal: true, titleFontSize: titleFontSize });
} else {
this.formBuilder.addFormItem(this.targetNoActiveConnectionsText, { horizontal: true, titleFontSize: titleFontSize });
}
this.formBuilder.addFormItem(this.targetServerComponent, { horizontal: true, titleFontSize: titleFontSize });
this.formBuilder.addFormItem(this.targetDatabaseComponent, { horizontal: true, titleFontSize: titleFontSize });
this.populateServerDropdown(true);
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
@@ -462,17 +459,22 @@ export class SchemaCompareDialog {
{
editable: true,
fireOnTextChange: true,
ariaLabel: loc.sourceServer
ariaLabel: loc.sourceServer,
width: this.textBoxWidth
}
).component();
this.sourceConnectionButton = this.createConnectionButton(false);
this.sourceServerDropdown.onValueChanged(async (value) => {
if (this.sourceServerDropdown.values.findIndex(x => this.matchesValue(x, value as string)) === -1) {
if (value.selected && this.sourceServerDropdown.values.findIndex(x => this.matchesValue(x, value.selected)) === -1) {
await this.sourceDatabaseDropdown.updateProperties({
values: [],
value: ' '
});
}
else {
this.sourceConnectionButton.iconPath = path.join(this.extensionContext.extensionPath, 'media', 'connect.svg');
await this.populateDatabaseDropdown((this.sourceServerDropdown.value as ConnectionDropdownValue).connection, false);
}
});
@@ -482,26 +484,57 @@ export class SchemaCompareDialog {
return {
component: this.sourceServerDropdown,
title: loc.ServerDropdownLabel
title: loc.ServerDropdownLabel,
actions: [this.sourceConnectionButton]
};
}
private createConnectionButton(isTarget: boolean): azdata.ButtonComponent {
const selectConnectionButton = this.view.modelBuilder.button().withProperties({
ariaLabel: loc.selectConnection,
iconPath: path.join(this.extensionContext.extensionPath, 'media', 'selectConnection.svg'),
height: '20px',
width: '20px'
}).component();
selectConnectionButton.onDidClick(async () => {
await this.connectionButtonClick(isTarget);
selectConnectionButton.iconPath = path.join(this.extensionContext.extensionPath, 'media', 'connect.svg');
});
return selectConnectionButton;
}
public async connectionButtonClick(isTarget: boolean): Promise<void> {
let connection = await azdata.connection.openConnectionDialog();
if (connection) {
this.connectionId = connection.connectionId;
this.promise = this.populateServerDropdown(isTarget);
this.promise2 = this.populateServerDropdown(!isTarget, true); // passively populate the other server dropdown as well to add the new connections
}
}
protected createTargetServerDropdown(): azdata.FormComponent {
this.targetServerDropdown = this.view.modelBuilder.dropDown().withProperties(
{
editable: true,
fireOnTextChange: true,
ariaLabel: loc.targetServer
ariaLabel: loc.targetServer,
width: this.textBoxWidth
}
).component();
this.targetConnectionButton = this.createConnectionButton(true);
this.targetServerDropdown.onValueChanged(async (value) => {
if (this.targetServerDropdown.values.findIndex(x => this.matchesValue(x, value as string)) === -1) {
if (value.selected && this.targetServerDropdown.values.findIndex(x => this.matchesValue(x, value.selected)) === -1) {
await this.targetDatabaseDropdown.updateProperties({
values: [],
value: ' '
});
}
else {
this.targetConnectionButton.iconPath = path.join(this.extensionContext.extensionPath, 'media', 'connect.svg');
await this.populateDatabaseDropdown((this.targetServerDropdown.value as ConnectionDropdownValue).connection, true);
}
});
@@ -511,25 +544,39 @@ export class SchemaCompareDialog {
return {
component: this.targetServerDropdown,
title: loc.ServerDropdownLabel
title: loc.ServerDropdownLabel,
actions: [this.targetConnectionButton]
};
}
protected async populateServerDropdown(isTarget: boolean): Promise<void> {
protected async populateServerDropdown(isTarget: boolean, passivelyPopulate: boolean = false): Promise<void> {
const currentDropdown = isTarget ? this.targetServerDropdown : this.sourceServerDropdown;
if (passivelyPopulate && isNullOrUndefined(currentDropdown.value)) {
passivelyPopulate = false; // Populate the dropdown if it is empty
}
currentDropdown.loading = true;
const values = await this.getServerValues(isTarget);
if (values && values.length > 0) {
await currentDropdown.updateProperties({
values: values,
value: values[0]
});
if (passivelyPopulate) { // only update the dropdown values, not the selected value
await currentDropdown.updateProperties({
values: values
});
} else {
await currentDropdown.updateProperties({
values: values,
value: values[0]
});
}
}
currentDropdown.loading = false;
await this.populateDatabaseDropdown((currentDropdown.value as ConnectionDropdownValue).connection, isTarget);
if (!passivelyPopulate && currentDropdown.value) {
await this.populateDatabaseDropdown((currentDropdown.value as ConnectionDropdownValue).connection, isTarget);
}
}
protected async getServerValues(isTarget: boolean): Promise<{ connection: azdata.connection.ConnectionProfile, displayName: string, name: string }[]> {
@@ -539,6 +586,10 @@ export class SchemaCompareDialog {
return undefined;
}
// Update connection icon to "connected" state
let connectionButton = isTarget ? this.targetConnectionButton : this.sourceConnectionButton;
connectionButton.iconPath = path.join(this.extensionContext.extensionPath, 'media', 'connect.svg');
let endpointInfo = isTarget ? this.schemaCompareMainWindow.targetEndpointInfo : this.schemaCompareMainWindow.sourceEndpointInfo;
// reverse list so that most recent connections are first
cons.reverse();
@@ -603,7 +654,8 @@ export class SchemaCompareDialog {
{
editable: true,
fireOnTextChange: true,
ariaLabel: loc.sourceDatabase
ariaLabel: loc.sourceDatabase,
width: this.textBoxWidth
}
).component();
this.sourceDatabaseDropdown.onValueChanged(async (value) => {
@@ -622,7 +674,8 @@ export class SchemaCompareDialog {
{
editable: true,
fireOnTextChange: true,
ariaLabel: loc.targetDatabase
ariaLabel: loc.targetDatabase,
width: this.textBoxWidth
}
).component();
this.targetDatabaseDropdown.onValueChanged(async (value) => {
@@ -643,7 +696,10 @@ export class SchemaCompareDialog {
protected async populateDatabaseDropdown(connectionProfile: azdata.connection.ConnectionProfile, isTarget: boolean): Promise<void> {
const currentDropdown = isTarget ? this.targetDatabaseDropdown : this.sourceDatabaseDropdown;
currentDropdown.loading = true;
await currentDropdown.updateProperties({ values: [], value: null });
await currentDropdown.updateProperties({
values: [],
value: undefined
});
let values = [];
try {
@@ -687,21 +743,13 @@ export class SchemaCompareDialog {
}
return values;
}
protected createNoActiveConnectionsText(): azdata.FormComponent {
let noActiveConnectionsText = this.view.modelBuilder.text().withProperties({ value: loc.NoActiveConnectionsLabel }).component();
return {
component: noActiveConnectionsText,
title: ''
};
}
}
interface ConnectionDropdownValue extends azdata.CategoryValue {
export interface ConnectionDropdownValue extends azdata.CategoryValue {
connection: azdata.connection.ConnectionProfile;
}
function isNullOrUndefined(val: any): boolean {
return val === null || val === undefined;
}