Add IR Migration configuration Validation to SQL Migration extension (#21386)

* re-factor and consolidate wizard pages

* validation WIP 11/10

* validate ir dialog

* navigation fixes

* bump version to 1.2.0

* add resource strings and fix navigatin issue

* map validation state to resource string clean up

* address review comments

* fix typos, address review comments

* address review feedback, readability

* fix res string, validation check, col width

* bug fixes, nav, sqldb migration

* fix nav/refresh/visibility issues

* fix nav issues, cancel pending validation items

* update error text / position

* fix localization bug
This commit is contained in:
brian-harris
2022-12-16 14:52:24 -08:00
committed by GitHub
parent 754d70d654
commit 2e240729af
29 changed files with 1993 additions and 692 deletions

View File

@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
import { EOL } from 'os';
import { getStorageAccountAccessKeys } from '../api/azure';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { Blob, MigrationMode, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, NetworkShare, StateChangeEvent } from '../models/stateMachine';
import { Blob, MigrationMode, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, NetworkShare, StateChangeEvent, ValidateIrState, ValidationResult } from '../models/stateMachine';
import * as constants from '../constants/strings';
import { IconPathHelper } from '../constants/iconPathHelper';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
@@ -16,9 +16,11 @@ import * as utils from '../api/utils';
import { logError, TelemetryViews } from '../telemtery';
import * as styles from '../constants/styles';
import { TableMigrationSelectionDialog } from '../dialog/tableMigrationSelection/tableMigrationSelectionDialog';
import { ValidateIrDialog } from '../dialog/validationResults/validateIrDialog';
const WIZARD_TABLE_COLUMN_WIDTH = '200px';
const WIZARD_TABLE_COLUMN_WIDTH_SMALL = '170px';
const VALIDATE_IR_CUSTOM_BUTTON_INDEX = 0;
const blobResourceGroupErrorStrings = [constants.RESOURCE_GROUP_NOT_FOUND];
const blobStorageAccountErrorStrings = [constants.NO_STORAGE_ACCOUNT_FOUND, constants.SELECT_RESOURCE_GROUP_PROMPT];
@@ -28,9 +30,6 @@ const blobFileErrorStrings = [constants.NO_BLOBFILES_FOUND, constants.SELECT_BLO
export class DatabaseBackupPage extends MigrationWizardPage {
private _view!: azdata.ModelView;
private _networkShareButton!: azdata.RadioButtonComponent;
private _blobContainerButton!: azdata.RadioButtonComponent;
private _sourceConnectionContainer!: azdata.FlexContainer;
private _networkShareContainer!: azdata.FlexContainer;
private _windowsUserAccountText!: azdata.InputBoxComponent;
@@ -62,7 +61,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private _networkShareTargetDatabaseNames: azdata.InputBoxComponent[] = [];
private _blobContainerTargetDatabaseNames: azdata.InputBoxComponent[] = [];
private _networkShareLocations: azdata.InputBoxComponent[] = [];
private _radioButtonContainer!: azdata.FlexContainer;
private _networkDetailsContainer!: azdata.FlexContainer;
private _existingDatabases: string[] = [];
@@ -81,21 +79,24 @@ export class DatabaseBackupPage extends MigrationWizardPage {
protected async registerContent(view: azdata.ModelView): Promise<void> {
this._view = view;
this._radioButtonContainer = this.createBackupLocationComponent();
this._sourceConnectionContainer = this.createSourceCredentialsContainer();
this._networkDetailsContainer = this.createNetworkDetailsContainer();
this._targetDatabaseContainer = this.createTargetDatabaseContainer();
this._networkShareStorageAccountDetails = this.createNetworkShareStorageAccountDetailsContainer();
this._migrationTableSection = this._migrationTableSelectionContainer();
this._disposables.push(
this.wizard.customButtons[VALIDATE_IR_CUSTOM_BUTTON_INDEX].onClick(
async e => await this._validateIr()));
const form = this._view.modelBuilder.formContainer()
.withFormItems([
{ title: '', component: this._radioButtonContainer },
{ title: '', component: this._sourceConnectionContainer },
{ title: '', component: this._networkDetailsContainer },
{ title: '', component: this._migrationTableSection },
{ title: '', component: this._networkShareStorageAccountDetails },
{ title: '', component: this._targetDatabaseContainer },
{ title: '', component: this._networkShareStorageAccountDetails }])
{ title: '', component: this._migrationTableSection },
])
.withProps({ CSSStyles: { 'padding-top': '0' } })
.component();
@@ -108,56 +109,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
await view.initializeModel(form);
}
private createBackupLocationComponent(): azdata.FlexContainer {
const buttonGroup = 'networkContainer';
const selectLocationText = this._view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_PAGE_DESCRIPTION,
CSSStyles: { ...styles.BODY_CSS }
}).component();
this._networkShareButton = this._view.modelBuilder.radioButton()
.withProps({
name: buttonGroup,
label: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL,
checked: this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE,
CSSStyles: { ...styles.BODY_CSS, 'margin': '0' }
}).component();
this._disposables.push(
this._networkShareButton.onDidChangeCheckedState(async checked => {
if (checked) {
await this.switchNetworkContainerFields(NetworkContainerType.NETWORK_SHARE);
}
}));
this._blobContainerButton = this._view.modelBuilder.radioButton()
.withProps({
name: buttonGroup,
label: constants.DATABASE_BACKUP_NC_BLOB_STORAGE_RADIO_LABEL,
checked: this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER,
CSSStyles: { ...styles.BODY_CSS, 'margin': '0' }
}).component();
this._disposables.push(
this._blobContainerButton.onDidChangeCheckedState(async checked => {
if (checked) {
await this.switchNetworkContainerFields(NetworkContainerType.BLOB_CONTAINER);
}
}));
const flexContainer = this._view.modelBuilder.flexContainer()
.withItems([
selectLocationText,
this._networkShareButton,
this._blobContainerButton])
.withLayout({ flexFlow: 'column' })
.component();
return flexContainer;
}
private createNetworkDetailsContainer(): azdata.FlexContainer {
this._networkShareContainer = this.createNetworkShareContainer();
this._blobContainer = this.createBlobContainer();
@@ -200,7 +151,10 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}).component();
this._disposables.push(
this._sqlSourceUsernameInput.onTextChanged(
value => this.migrationStateModel._sqlServerUsername = value));
value => {
this.migrationStateModel._sqlServerUsername = value;
this._resetValidationUI();
}));
const sqlPasswordLabel = this._view.modelBuilder.text()
.withProps({
@@ -218,7 +172,10 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}).component();
this._disposables.push(
this._sqlSourcePassword.onTextChanged(
value => this.migrationStateModel._sqlServerPassword = value));
value => {
this.migrationStateModel._sqlServerPassword = value;
this._resetValidationUI();
}));
return this._view.modelBuilder.flexContainer()
.withItems([
@@ -273,7 +230,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
CSSStyles: { ...styles.BODY_CSS, 'margin-top': '-1em' }
})
.withValidation((component) => {
if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
if (this.migrationStateModel.isBackupContainerNetworkShare) {
if (component.value) {
if (!/^[A-Za-z0-9\\\._-]{7,}$/.test(component.value)) {
return false;
@@ -287,6 +244,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
for (let i = 0; i < this.migrationStateModel._databaseBackup.networkShares.length; i++) {
this.migrationStateModel._databaseBackup.networkShares[i].windowsUser = value;
}
this._resetValidationUI();
}));
const passwordLabel = this._view.modelBuilder.text()
@@ -309,6 +267,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
for (let i = 0; i < this.migrationStateModel._databaseBackup.networkShares.length; i++) {
this.migrationStateModel._databaseBackup.networkShares[i].password = value;
}
this._resetValidationUI();
}));
return this._view.modelBuilder.flexContainer()
@@ -325,6 +284,13 @@ export class DatabaseBackupPage extends MigrationWizardPage {
.component();
}
private _resetValidationUI(): void {
if (this.wizard.message.level === azdata.window.MessageLevel.Information) {
this.wizard.message = { text: '' };
}
this.migrationStateModel.resetIrValidationResults();
}
private createBlobContainer(): azdata.FlexContainer {
const blobHeading = this._view.modelBuilder.text()
.withProps({
@@ -612,6 +578,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
for (let i = 0; i < this.migrationStateModel._databaseBackup.networkShares.length; i++) {
this.migrationStateModel._databaseBackup.networkShares[i].storageAccount = selectedStorageAccount;
}
this.migrationStateModel.resetIrValidationResults();
}
}
}));
@@ -658,40 +625,130 @@ export class DatabaseBackupPage extends MigrationWizardPage {
.component();
}
private async _updatePageControlsVisibility(containerType: NetworkContainerType): Promise<void> {
const isSqlDbTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLDB;
const isNetworkShare = containerType === NetworkContainerType.NETWORK_SHARE;
const isBlobContainer = containerType === NetworkContainerType.BLOB_CONTAINER;
private async _updatePageControlsVisibility(): Promise<void> {
const isSqlDbTarget = this.migrationStateModel.isSqlDbTarget;
const isNetworkShare = this.migrationStateModel.isBackupContainerNetworkShare;
const isBlobContainer = this.migrationStateModel.isBackupContainerBlobContainer;
await utils.updateControlDisplay(this._sourceConnectionContainer, isSqlDbTarget || isNetworkShare);
await utils.updateControlDisplay(this._migrationTableSection, isSqlDbTarget);
await utils.updateControlDisplay(this._radioButtonContainer, !isSqlDbTarget);
await utils.updateControlDisplay(this._networkDetailsContainer, !isSqlDbTarget);
await utils.updateControlDisplay(this._targetDatabaseContainer, !isSqlDbTarget);
await utils.updateControlDisplay(this._networkShareStorageAccountDetails, !isSqlDbTarget);
await utils.updateControlDisplay(this._networkShareContainer, isNetworkShare);
await utils.updateControlDisplay(this._networkShareStorageAccountDetails, isNetworkShare);
await utils.updateControlDisplay(this._networkTableContainer, isNetworkShare);
await utils.updateControlDisplay(this._blobContainer, isBlobContainer);
await utils.updateControlDisplay(this._blobTableContainer, isBlobContainer);
await utils.updateControlDisplay(this._networkShareContainer, isNetworkShare && !isSqlDbTarget);
await utils.updateControlDisplay(this._networkShareStorageAccountDetails, isNetworkShare && !isSqlDbTarget);
await utils.updateControlDisplay(this._networkTableContainer, isNetworkShare && !isSqlDbTarget);
await utils.updateControlDisplay(this._blobContainer, isBlobContainer && !isSqlDbTarget);
await utils.updateControlDisplay(this._blobTableContainer, isBlobContainer && !isSqlDbTarget);
await this._windowsUserAccountText.updateProperties({ required: isNetworkShare });
await this._passwordText.updateProperties({ required: isNetworkShare });
await this._windowsUserAccountText.updateProperties({ required: isNetworkShare && !isSqlDbTarget });
await this._passwordText.updateProperties({ required: isNetworkShare && !isSqlDbTarget });
await this._sqlSourceUsernameInput.updateProperties({ required: isNetworkShare || isSqlDbTarget });
await this._sqlSourcePassword.updateProperties({ required: isNetworkShare || isSqlDbTarget });
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return;
}
if (this.migrationStateModel.refreshDatabaseBackupPage) {
this._networkShareButton.checked = this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE;
this._blobContainerButton.checked = this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER;
await this._updatePageControlsVisibility(this.migrationStateModel._databaseBackup.networkContainerType);
this.wizard.registerNavigationValidator((pageChangeInfo) => {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return true;
}
const isSqlDbTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLDB;
this.wizard.message = { text: '' };
const errors: string[] = [];
const isSqlDbTarget = this.migrationStateModel.isSqlDbTarget;
if (isSqlDbTarget) {
if (!this._validateTableSelection()) {
errors.push(constants.DATABASE_TABLE_VALIDATE_SELECTION_MESSAGE);
}
} else {
switch (this.migrationStateModel._databaseBackup.networkContainerType) {
case NetworkContainerType.NETWORK_SHARE:
if ((<azdata.CategoryValue>this._networkShareStorageAccountResourceGroupDropdown.value)?.displayName === constants.RESOURCE_GROUP_NOT_FOUND) {
errors.push(constants.INVALID_RESOURCE_GROUP_ERROR);
}
if ((<azdata.CategoryValue>this._networkShareContainerStorageAccountDropdown.value)?.displayName === constants.NO_STORAGE_ACCOUNT_FOUND) {
errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
}
break;
case NetworkContainerType.BLOB_CONTAINER:
this._blobContainerResourceGroupDropdowns.forEach((v, index) => {
if (this.shouldDisplayBlobDropdownError(v, [constants.RESOURCE_GROUP_NOT_FOUND])) {
errors.push(constants.INVALID_BLOB_RESOURCE_GROUP_ERROR(this.migrationStateModel._databasesForMigration[index]));
}
});
this._blobContainerStorageAccountDropdowns.forEach((v, index) => {
if (this.shouldDisplayBlobDropdownError(v, [constants.NO_STORAGE_ACCOUNT_FOUND, constants.SELECT_RESOURCE_GROUP_PROMPT])) {
errors.push(constants.INVALID_BLOB_STORAGE_ACCOUNT_ERROR(this.migrationStateModel._databasesForMigration[index]));
}
});
this._blobContainerDropdowns.forEach((v, index) => {
if (this.shouldDisplayBlobDropdownError(v, [constants.NO_BLOBCONTAINERS_FOUND, constants.SELECT_STORAGE_ACCOUNT])) {
errors.push(constants.INVALID_BLOB_CONTAINER_ERROR(this.migrationStateModel._databasesForMigration[index]));
}
});
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
this._blobContainerLastBackupFileDropdowns.forEach((v, index) => {
if (this.shouldDisplayBlobDropdownError(v, [constants.NO_BLOBFILES_FOUND, constants.SELECT_BLOB_CONTAINER])) {
errors.push(constants.INVALID_BLOB_LAST_BACKUP_FILE_ERROR(this.migrationStateModel._databasesForMigration[index]));
}
});
}
if (errors.length > 0) {
const duplicates: Map<string, number[]> = new Map();
for (let i = 0; i < this.migrationStateModel._targetDatabaseNames.length; i++) {
const blobContainerId = this.migrationStateModel._databaseBackup.blobs[i].blobContainer?.id;
if (duplicates.has(blobContainerId)) {
duplicates.get(blobContainerId)?.push(i);
} else {
duplicates.set(blobContainerId, [i]);
}
}
duplicates.forEach((d) => {
if (d.length > 1) {
const dupString = `${d.map(index => this.migrationStateModel._databasesForMigration[index]).join(', ')}`;
errors.push(constants.PROVIDE_UNIQUE_CONTAINERS + dupString);
}
});
}
break;
}
}
if (this.migrationStateModel.isSqlMiTarget) {
this.migrationStateModel._targetDatabaseNames.forEach(t => {
// Making sure if database with same name is not present on the target Azure SQL
if (this._existingDatabases.includes(t)) {
errors.push(constants.DATABASE_ALREADY_EXISTS_MI(t, this.migrationStateModel._targetServerInstance.name));
}
});
}
this.wizard.message = {
text: errors.join(EOL),
level: azdata.window.MessageLevel.Error
};
if (errors.length > 0) {
return false;
}
if (this.migrationStateModel.isIrMigration) {
this.wizard.nextButton.enabled = this.migrationStateModel.isIrTargetValidated;
this.updateValidationResultUI();
return this.migrationStateModel.isIrTargetValidated;
} else {
return true;
}
});
this.wizard.customButtons[VALIDATE_IR_CUSTOM_BUTTON_INDEX].hidden = !this.migrationStateModel.isIrMigration;
await this._updatePageControlsVisibility();
if (this.migrationStateModel.refreshDatabaseBackupPage) {
const isSqlDbTarget = this.migrationStateModel.isSqlDbTarget;
if (isSqlDbTarget) {
this.wizardPage.title = constants.DATABASE_TABLE_SELECTION_LABEL;
this.wizardPage.description = constants.DATABASE_TABLE_SELECTION_LABEL;
@@ -700,7 +757,15 @@ export class DatabaseBackupPage extends MigrationWizardPage {
try {
const isOfflineMigration = this.migrationStateModel._databaseBackup?.migrationMode === MigrationMode.OFFLINE;
const lastBackupFileColumnIndex = this._blobContainerTargetDatabaseNamesTable.columns.length - 1;
this._blobContainerTargetDatabaseNamesTable.columns[lastBackupFileColumnIndex].hidden = !isOfflineMigration;
const oldHidden = this._blobContainerTargetDatabaseNamesTable.columns[lastBackupFileColumnIndex].hidden;
const newHidden = !isOfflineMigration;
if (oldHidden !== newHidden) {
// clear values prior to hiding columns if changing column visibility
// to prevent null DeclarativeTableComponent - exception / _view null
await this._blobContainerTargetDatabaseNamesTable.setDataValues([]);
}
this._blobContainerTargetDatabaseNamesTable.columns[lastBackupFileColumnIndex].hidden = newHidden;
this._blobContainerTargetDatabaseNamesTable.columns.forEach(column => {
column.width = isOfflineMigration
? WIZARD_TABLE_COLUMN_WIDTH_SMALL
@@ -712,26 +777,37 @@ export class DatabaseBackupPage extends MigrationWizardPage {
(await this.migrationStateModel.getSourceConnectionProfile()).providerId,
azdata.DataProviderType.QueryProvider);
const query = 'select SUSER_NAME()';
const results = await queryProvider.runQueryAndReturn(
await (azdata.connection.getUriForConnection(
this.migrationStateModel.sourceConnectionId)), query);
let username = '';
try {
const query = 'select SUSER_NAME()';
const ownerUri = await azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId);
const results = await queryProvider.runQueryAndReturn(ownerUri, query);
username = results.rows[0][0]?.displayValue;
} catch (e) {
username = connectionProfile.userName;
}
const username = results.rows[0][0].displayValue;
this.migrationStateModel._authenticationType = connectionProfile.authenticationType === 'SqlLogin'
? MigrationSourceAuthenticationType.Sql
: connectionProfile.authenticationType === 'Integrated' // TODO: use azdata.connection.AuthenticationType.Integrated after next ADS release
? MigrationSourceAuthenticationType.Integrated
: undefined!;
this.migrationStateModel._authenticationType =
connectionProfile.authenticationType === azdata.connection.AuthenticationType.SqlLogin
? MigrationSourceAuthenticationType.Sql
: connectionProfile.authenticationType === azdata.connection.AuthenticationType.Integrated
? MigrationSourceAuthenticationType.Integrated
: undefined!;
this._sourceHelpText.value = constants.SQL_SOURCE_DETAILS(
this.migrationStateModel._authenticationType,
connectionProfile.serverName);
this._sqlSourceUsernameInput.value = username;
this._sqlSourcePassword.value = (await azdata.connection.getCredentials(this.migrationStateModel.sourceConnectionId)).password;
this._windowsUserAccountText.value = this.migrationStateModel.savedInfo?.networkShares
? this.migrationStateModel.savedInfo?.networkShares[0]?.windowsUser
: '';
this._windowsUserAccountText.value =
this.migrationStateModel._databaseBackup.networkShares[0]?.windowsUser
?? this.migrationStateModel.savedInfo?.networkShares[0]?.windowsUser
?? '';
this._passwordText.value =
this.migrationStateModel._databaseBackup.networkShares[0]?.password
?? this.migrationStateModel.savedInfo?.networkShares[0]?.password
?? '';
this._networkShareTargetDatabaseNames = [];
this._networkShareLocations = [];
@@ -741,7 +817,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._blobContainerDropdowns = [];
this._blobContainerLastBackupFileDropdowns = [];
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) {
if (this.migrationStateModel.isSqlMiTarget) {
this._existingDatabases = await this.migrationStateModel.getManagedDatabases();
}
@@ -803,7 +879,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return false;
}
// Making sure if database with same name is not present on the target Azure SQL
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI && this._existingDatabases.includes(c.value!)) {
if (this.migrationStateModel.isSqlMiTarget && this._existingDatabases.includes(c.value!)) {
c.validationErrorMessage = constants.DATABASE_ALREADY_EXISTS_MI(c.value!, this.migrationStateModel._targetServerInstance.name);
return false;
}
@@ -816,7 +892,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._disposables.push(
targetDatabaseInput.onTextChanged(async (value) => {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
await this.validateFields();
this._resetValidationUI();
await this.validateFields(targetDatabaseInput);
}));
targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index];
this._networkShareTargetDatabaseNames.push(targetDatabaseInput);
@@ -828,7 +905,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
validationErrorMessage: constants.INVALID_NETWORK_SHARE_LOCATION,
width: '300px'
}).withValidation(c => {
if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
if (this.migrationStateModel.isBackupContainerNetworkShare) {
if (c.value) {
if (!/^[\\\/]{2,}[^\\\/]+[\\\/]+[^\\\/]+/.test(c.value)) {
return false;
@@ -840,7 +917,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._disposables.push(
networkShareLocationInput.onTextChanged(async (value) => {
this.migrationStateModel._databaseBackup.networkShares[index].networkShareLocation = value.trim();
await this.validateFields();
this._resetValidationUI();
await this.validateFields(networkShareLocationInput);
}));
networkShareLocationInput.value = this.migrationStateModel._databaseBackup.networkShares[index]?.networkShareLocation;
this._networkShareLocations.push(networkShareLocationInput);
@@ -856,7 +934,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return false;
}
// Making sure if database with same name is not present on the target Azure SQL
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI && this._existingDatabases.includes(c.value!)) {
if (this.migrationStateModel.isSqlMiTarget && this._existingDatabases.includes(c.value!)) {
c.validationErrorMessage = constants.DATABASE_ALREADY_EXISTS_MI(c.value!, this.migrationStateModel._targetServerInstance.name);
return false;
}
@@ -868,7 +946,10 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}).component();
this._disposables.push(
blobTargetDatabaseInput.onTextChanged(
(value) => { this.migrationStateModel._targetDatabaseNames[index] = value.trim(); }));
(value) => {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
this._resetValidationUI();
}));
targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index];
this._blobContainerTargetDatabaseNames.push(blobTargetDatabaseInput);
@@ -987,13 +1068,17 @@ export class DatabaseBackupPage extends MigrationWizardPage {
{ value: this._blobContainerStorageAccountDropdowns[index] },
{ value: this._blobContainerDropdowns[index] },
{ value: this._blobContainerLastBackupFileDropdowns[index] }]);
await this._blobContainerTargetDatabaseNamesTable.setDataValues([]);
await this._blobContainerTargetDatabaseNamesTable.setDataValues(blobContainerTargetData);
await this.getSubscriptionValues();
// clear change tracking flags
this.migrationStateModel.refreshDatabaseBackupPage = false;
this.migrationStateModel._didUpdateDatabasesForMigration = false;
this.migrationStateModel._didDatabaseMappingChange = false;
this.migrationStateModel._validateIrSqlDb = [];
this.migrationStateModel._validateIrSqlMi = [];
this.migrationStateModel._validateIrSqlVm = [];
} catch (error) {
console.log(error);
let errorText = error?.message;
@@ -1006,101 +1091,76 @@ export class DatabaseBackupPage extends MigrationWizardPage {
level: azdata.window.MessageLevel.Error
};
}
await this.validateFields();
this.updateValidationResultUI(true);
}
}
private async _validateIr(): Promise<void> {
this.wizard.message = { text: '' };
const dialog = new ValidateIrDialog(
this.migrationStateModel,
() => this.updateValidationResultUI());
let results: ValidationResult[] = [];
switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLDB:
results = this.migrationStateModel._validateIrSqlDb;
break;
case MigrationTargetType.SQLMI:
results = this.migrationStateModel._validateIrSqlMi;
break;
case MigrationTargetType.SQLVM:
results = this.migrationStateModel._validateIrSqlVm;
break;
}
this.wizard.registerNavigationValidator((pageChangeInfo) => {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return true;
await dialog.openDialog(constants.VALIDATION_DIALOG_TITLE, results);
}
public updateValidationResultUI(initializing?: boolean): void {
if (this.migrationStateModel.isIrMigration) {
const succeeded = this.migrationStateModel.isIrTargetValidated;
if (succeeded) {
this.wizard.message = {
level: azdata.window.MessageLevel.Information,
text: constants.VALIDATION_MESSAGE_SUCCESS,
};
} else {
const results = this.migrationStateModel.validationTargetResults;
const hasResults = results.length > 0;
if (initializing && !hasResults) {
return;
}
const canceled = results.some(result => result.state === ValidateIrState.Canceled);
const errors: string[] = results.flatMap(result => result.errors) ?? [];
const errorsMessage: string = errors.join(EOL);
const hasErrors = errors.length > 0;
const msg = hasResults
? hasErrors
? canceled
? constants.VALIDATION_MESSAGE_CANCELED_ERRORS(errorsMessage)
: constants.VALIDATE_IR_VALIDATION_COMPLETED_ERRORS(errorsMessage)
: constants.VALIDATION_MESSAGE_CANCELED
: constants.VALIDATION_MESSAGE_NOT_RUN;
this.wizard.message = {
level: azdata.window.MessageLevel.Error,
text: msg,
};
}
this.wizard.message = { text: '' };
const errors: string[] = [];
switch (this.migrationStateModel._databaseBackup.networkContainerType) {
case NetworkContainerType.NETWORK_SHARE:
if ((<azdata.CategoryValue>this._networkShareStorageAccountResourceGroupDropdown.value)?.displayName === constants.RESOURCE_GROUP_NOT_FOUND) {
errors.push(constants.INVALID_RESOURCE_GROUP_ERROR);
}
if ((<azdata.CategoryValue>this._networkShareContainerStorageAccountDropdown.value)?.displayName === constants.NO_STORAGE_ACCOUNT_FOUND) {
errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
}
break;
case NetworkContainerType.BLOB_CONTAINER:
this._blobContainerResourceGroupDropdowns.forEach((v, index) => {
if (this.shouldDisplayBlobDropdownError(v, [constants.RESOURCE_GROUP_NOT_FOUND])) {
errors.push(constants.INVALID_BLOB_RESOURCE_GROUP_ERROR(this.migrationStateModel._databasesForMigration[index]));
}
});
this._blobContainerStorageAccountDropdowns.forEach((v, index) => {
if (this.shouldDisplayBlobDropdownError(v, [constants.NO_STORAGE_ACCOUNT_FOUND, constants.SELECT_RESOURCE_GROUP_PROMPT])) {
errors.push(constants.INVALID_BLOB_STORAGE_ACCOUNT_ERROR(this.migrationStateModel._databasesForMigration[index]));
}
});
this._blobContainerDropdowns.forEach((v, index) => {
if (this.shouldDisplayBlobDropdownError(v, [constants.NO_BLOBCONTAINERS_FOUND, constants.SELECT_STORAGE_ACCOUNT])) {
errors.push(constants.INVALID_BLOB_CONTAINER_ERROR(this.migrationStateModel._databasesForMigration[index]));
}
});
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
this._blobContainerLastBackupFileDropdowns.forEach((v, index) => {
if (this.shouldDisplayBlobDropdownError(v, [constants.NO_BLOBFILES_FOUND, constants.SELECT_BLOB_CONTAINER])) {
errors.push(constants.INVALID_BLOB_LAST_BACKUP_FILE_ERROR(this.migrationStateModel._databasesForMigration[index]));
}
});
}
if (errors.length > 0) {
const duplicates: Map<string, number[]> = new Map();
for (let i = 0; i < this.migrationStateModel._targetDatabaseNames.length; i++) {
const blobContainerId = this.migrationStateModel._databaseBackup.blobs[i].blobContainer?.id;
if (duplicates.has(blobContainerId)) {
duplicates.get(blobContainerId)?.push(i);
} else {
duplicates.set(blobContainerId, [i]);
}
}
duplicates.forEach((d) => {
if (d.length > 1) {
const dupString = `${d.map(index => this.migrationStateModel._databasesForMigration[index]).join(', ')}`;
errors.push(constants.PROVIDE_UNIQUE_CONTAINERS + dupString);
}
});
}
break;
default:
const isSqlDbTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLDB;
if (isSqlDbTarget) {
if (!this._validateTableSelection()) {
errors.push(constants.DATABASE_TABLE_VALIDATE_SELECTION_MESSAGE);
}
break;
}
return false;
}
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) {
this.migrationStateModel._targetDatabaseNames.forEach(t => {
// Making sure if database with same name is not present on the target Azure SQL
if (this._existingDatabases.includes(t)) {
errors.push(constants.DATABASE_ALREADY_EXISTS_MI(t, this.migrationStateModel._targetServerInstance.name));
}
});
}
this.wizard.message = {
text: errors.join(EOL),
level: azdata.window.MessageLevel.Error
};
if (errors.length > 0) {
return false;
}
return true;
});
}
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
try {
if (pageChangeInfo.newPage > pageChangeInfo.lastPage) {
this.wizard.registerNavigationValidator(pageChangeInfo => true);
this.wizard.message = { text: '' };
this.wizard.customButtons[VALIDATE_IR_CUSTOM_BUTTON_INDEX].hidden = true;
if (pageChangeInfo.newPage > pageChangeInfo.lastPage) {
if (!this.migrationStateModel.isSqlDbTarget) {
switch (this.migrationStateModel._databaseBackup.networkContainerType) {
case NetworkContainerType.BLOB_CONTAINER:
for (let i = 0; i < this.migrationStateModel._databaseBackup.blobs.length; i++) {
@@ -1124,26 +1184,12 @@ export class DatabaseBackupPage extends MigrationWizardPage {
break;
}
}
} finally {
this.wizard.registerNavigationValidator((pageChangeInfo) => true);
}
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
}
private async switchNetworkContainerFields(containerType: NetworkContainerType): Promise<void> {
this.wizard.message = {
text: '',
level: azdata.window.MessageLevel.Error
};
this.wizard.nextButton.enabled = true;
this.migrationStateModel._databaseBackup.networkContainerType = containerType;
await this._updatePageControlsVisibility(containerType);
await this.validateFields();
}
private _validateTableSelection(): boolean {
for (const targetDatabaseInfo of this.migrationStateModel._sourceTargetMapping) {
const databaseInfo = targetDatabaseInfo[1];
@@ -1159,27 +1205,36 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return false;
}
private async validateFields(): Promise<void> {
await this._sqlSourceUsernameInput.validate();
await this._sqlSourcePassword.validate();
await this._windowsUserAccountText.validate();
await this._passwordText.validate();
await this._networkShareContainerSubscription.validate();
await this._networkShareStorageAccountResourceGroupDropdown.validate();
await this._networkShareContainerStorageAccountDropdown.validate();
await this._blobContainerSubscription.validate();
private async validateFields(component?: azdata.Component): Promise<void> {
await this._sqlSourceUsernameInput?.validate();
await this._sqlSourcePassword?.validate();
await this._windowsUserAccountText?.validate();
await this._passwordText?.validate();
await this._networkShareContainerSubscription?.validate();
await this._networkShareStorageAccountResourceGroupDropdown?.validate();
await this._networkShareContainerStorageAccountDropdown?.validate();
await this._blobContainerSubscription?.validate();
for (let i = 0; i < this._networkShareTargetDatabaseNames.length; i++) {
await this._networkShareTargetDatabaseNames[i].validate();
await this._networkShareLocations[i].validate();
await this._blobContainerTargetDatabaseNames[i].validate();
await this._blobContainerResourceGroupDropdowns[i].validate();
await this._blobContainerStorageAccountDropdowns[i].validate();
await this._blobContainerDropdowns[i].validate();
await this._networkShareTargetDatabaseNames[i]?.validate();
await this._networkShareLocations[i]?.validate();
await this._blobContainerTargetDatabaseNames[i]?.validate();
await this._blobContainerResourceGroupDropdowns[i]?.validate();
await this._blobContainerStorageAccountDropdowns[i]?.validate();
await this._blobContainerDropdowns[i]?.validate();
if (this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
await this._blobContainerLastBackupFileDropdowns[i]?.validate();
}
}
if (this.migrationStateModel.isIrMigration) {
if (this.migrationStateModel.isSqlDbTarget) {
await this._databaseTable?.validate();
}
}
if (this.migrationStateModel.isBackupContainerNetworkShare) {
await this._networkShareTargetDatabaseNamesTable.validate();
}
await component?.validate();
}
private async getSubscriptionValues(): Promise<void> {
@@ -1265,66 +1320,75 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
private loadBlobStorageDropdown(index: number): void {
try {
this._blobContainerStorageAccountDropdowns[index].loading = true;
this._blobContainerStorageAccountDropdowns[index].values = utils.getAzureResourceDropdownValues(
this.migrationStateModel._storageAccounts,
this.migrationStateModel._location,
this.migrationStateModel._databaseBackup.blobs[index]?.resourceGroup?.name,
constants.NO_STORAGE_ACCOUNT_FOUND);
const dropDown = this._blobContainerStorageAccountDropdowns[index];
if (dropDown) {
try {
dropDown.loading = true;
dropDown.values = utils.getAzureResourceDropdownValues(
this.migrationStateModel._storageAccounts,
this.migrationStateModel._location,
this.migrationStateModel._databaseBackup.blobs[index]?.resourceGroup?.name,
constants.NO_STORAGE_ACCOUNT_FOUND);
utils.selectDefaultDropdownValue(
this._blobContainerStorageAccountDropdowns[index],
this.migrationStateModel._databaseBackup?.blobs[index]?.storageAccount?.id,
false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobStorageDropdown', error);
} finally {
this._blobContainerStorageAccountDropdowns[index].loading = false;
utils.selectDefaultDropdownValue(
dropDown,
this.migrationStateModel._databaseBackup?.blobs[index]?.storageAccount?.id,
false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobStorageDropdown', error);
} finally {
dropDown.loading = false;
}
}
}
private async loadBlobContainerDropdown(index: number): Promise<void> {
try {
this._blobContainerDropdowns[index].loading = true;
this.migrationStateModel._blobContainers = await utils.getBlobContainer(
this.migrationStateModel._azureAccount,
this.migrationStateModel._databaseBackup.subscription,
this.migrationStateModel._databaseBackup.blobs[index]?.storageAccount);
const dropDown = this._blobContainerDropdowns[index];
if (dropDown) {
try {
dropDown.loading = true;
this.migrationStateModel._blobContainers = await utils.getBlobContainer(
this.migrationStateModel._azureAccount,
this.migrationStateModel._databaseBackup.subscription,
this.migrationStateModel._databaseBackup.blobs[index]?.storageAccount);
this._blobContainerDropdowns[index].values = utils.getResourceDropdownValues(
this.migrationStateModel._blobContainers,
constants.NO_BLOBCONTAINERS_FOUND);
dropDown.values = utils.getResourceDropdownValues(
this.migrationStateModel._blobContainers,
constants.NO_BLOBCONTAINERS_FOUND);
utils.selectDefaultDropdownValue(
this._blobContainerDropdowns[index],
this.migrationStateModel._databaseBackup?.blobs[index]?.blobContainer?.id,
false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobContainers', error);
} finally {
this._blobContainerDropdowns[index].loading = false;
utils.selectDefaultDropdownValue(
dropDown,
this.migrationStateModel._databaseBackup?.blobs[index]?.blobContainer?.id,
false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobContainers', error);
} finally {
dropDown.loading = false;
}
}
}
private async loadBlobLastBackupFileDropdown(index: number): Promise<void> {
try {
this._blobContainerLastBackupFileDropdowns[index].loading = true;
this.migrationStateModel._lastFileNames = await utils.getBlobLastBackupFileNames(
this.migrationStateModel._azureAccount,
this.migrationStateModel._databaseBackup.subscription,
this.migrationStateModel._databaseBackup.blobs[index]?.storageAccount,
this.migrationStateModel._databaseBackup.blobs[index]?.blobContainer);
this._blobContainerLastBackupFileDropdowns[index].values = await utils.getBlobLastBackupFileNamesValues(
this.migrationStateModel._lastFileNames);
utils.selectDefaultDropdownValue(
this._blobContainerLastBackupFileDropdowns[index],
this.migrationStateModel._databaseBackup?.blobs[index]?.lastBackupFile,
false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobLastBackupFiles', error);
} finally {
this._blobContainerLastBackupFileDropdowns[index].loading = false;
const dropDown = this._blobContainerLastBackupFileDropdowns[index];
if (dropDown) {
try {
dropDown.loading = true;
this.migrationStateModel._lastFileNames = await utils.getBlobLastBackupFileNames(
this.migrationStateModel._azureAccount,
this.migrationStateModel._databaseBackup.subscription,
this.migrationStateModel._databaseBackup.blobs[index]?.storageAccount,
this.migrationStateModel._databaseBackup.blobs[index]?.blobContainer);
dropDown.values = await utils.getBlobLastBackupFileNamesValues(
this.migrationStateModel._lastFileNames);
utils.selectDefaultDropdownValue(
dropDown,
this.migrationStateModel._databaseBackup?.blobs[index]?.lastBackupFile,
false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobLastBackupFiles', error);
} finally {
dropDown.loading = false;
}
}
}
@@ -1409,6 +1473,12 @@ export class DatabaseBackupPage extends MigrationWizardPage {
},
],
})
.withValidation(table => {
if (this.migrationStateModel.isSqlDbTarget) {
return this._validateTableSelection();
}
return true;
})
.component();
this._disposables.push(
@@ -1479,7 +1549,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
});
await this._databaseTable.updateProperty('data', data);
this._refreshLoading.loading = false;
}
}

View File

@@ -64,6 +64,9 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
}
public async onPageLeave(): Promise<void> {
this.wizard.registerNavigationValidator(pageChangeInfo => true);
this.wizard.message = { text: '' };
const assessedDatabases = this.migrationStateModel._assessedDatabaseList ?? [];
const selectedDatabases = this.migrationStateModel._databasesForAssessment;
// run assessment if
@@ -75,15 +78,6 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
|| assessedDatabases.length === 0
|| assessedDatabases.length !== selectedDatabases.length
|| assessedDatabases.some(db => selectedDatabases.indexOf(db) < 0);
this.wizard.message = {
text: '',
level: azdata.window.MessageLevel.Error
};
this.wizard.registerNavigationValidator((pageChangeInfo) => {
return true;
});
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
import { MigrationMode, MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
import { CreateSqlMigrationServiceDialog } from '../dialog/createSqlMigrationService/createSqlMigrationServiceDialog';
import * as constants from '../constants/strings';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
@@ -32,10 +32,22 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
private _copy2!: azdata.ButtonComponent;
private _refresh1!: azdata.ButtonComponent;
private _refresh2!: azdata.ButtonComponent;
private _onlineButton!: azdata.RadioButtonComponent;
private _offlineButton!: azdata.RadioButtonComponent;
private _modeContainer!: azdata.FlexContainer;
private _radioButtonContainer!: azdata.FlexContainer;
private _networkShareButton!: azdata.RadioButtonComponent;
private _blobContainerButton!: azdata.RadioButtonComponent;
private _originalMigrationMode!: MigrationMode;
private _disposables: vscode.Disposable[] = [];
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.IR_PAGE_TITLE), migrationStateModel);
this.migrationStateModel._databaseBackup.migrationMode =
this.migrationStateModel._databaseBackup.migrationMode ||
this.migrationStateModel.isSqlDbTarget
? MigrationMode.OFFLINE
: MigrationMode.ONLINE;
}
protected async registerContent(view: azdata.ModelView): Promise<void> {
@@ -49,8 +61,13 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
.withItems([this._statusLoadingComponent])
.component();
this._radioButtonContainer = this.createBackupLocationComponent();
this._modeContainer = this.migrationModeContainer();
const form = view.modelBuilder.formContainer()
.withFormItems([
{ component: this._modeContainer },
{ component: this._radioButtonContainer },
{ component: this.migrationServiceDropdownContainer() },
{ component: this._dmsInfoContainer }])
.withProps({ CSSStyles: { 'padding-top': '0' } })
@@ -64,26 +81,132 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
await view.initializeModel(form);
}
private migrationModeContainer(): azdata.FlexContainer {
const buttonGroup = 'migrationMode';
this._onlineButton = this._view.modelBuilder.radioButton()
.withProps({
label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
name: buttonGroup,
checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.ONLINE,
CSSStyles: { ...styles.LABEL_CSS, },
}).component();
const onlineDescription = this._view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION,
CSSStyles: { ...styles.NOTE_CSS, 'margin-left': '20px' }
}).component();
this._disposables.push(
this._onlineButton.onDidChangeCheckedState(checked => {
if (checked) {
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.ONLINE;
this.migrationStateModel.refreshDatabaseBackupPage = true;
}
}));
this._offlineButton = this._view.modelBuilder.radioButton()
.withProps({
label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
name: buttonGroup,
checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE,
CSSStyles: { ...styles.LABEL_CSS, 'margin-top': '12px' },
}).component();
const offlineDescription = this._view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_DESCRIPTION,
CSSStyles: { ...styles.NOTE_CSS, 'margin-left': '20px' }
}).component();
this._disposables.push(
this._offlineButton.onDidChangeCheckedState(checked => {
if (checked) {
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE;
this.migrationStateModel.refreshDatabaseBackupPage = true;
}
}));
const flexContainer = this._view.modelBuilder.flexContainer()
.withItems([
this._onlineButton,
onlineDescription,
this._offlineButton,
offlineDescription]
).withLayout({ flexFlow: 'column' })
.component();
return flexContainer;
}
private createBackupLocationComponent(): azdata.FlexContainer {
const buttonGroup = 'networkContainer';
const selectLocationText = this._view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_PAGE_DESCRIPTION,
CSSStyles: { ...styles.BODY_CSS }
}).component();
this._networkShareButton = this._view.modelBuilder.radioButton()
.withProps({
name: buttonGroup,
label: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL,
checked: this.migrationStateModel.isBackupContainerNetworkShare,
CSSStyles: { ...styles.BODY_CSS, 'margin': '0' }
}).component();
this._disposables.push(
this._networkShareButton.onDidChangeCheckedState(async checked => {
if (checked) {
this.migrationStateModel._databaseBackup.networkContainerType = NetworkContainerType.NETWORK_SHARE;
await utils.updateControlDisplay(this._dmsInfoContainer, true);
this.migrationStateModel.refreshDatabaseBackupPage = true;
}
}));
this._blobContainerButton = this._view.modelBuilder.radioButton()
.withProps({
name: buttonGroup,
label: constants.DATABASE_BACKUP_NC_BLOB_STORAGE_RADIO_LABEL,
checked: this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER,
CSSStyles: { ...styles.BODY_CSS, 'margin': '0' }
}).component();
this._disposables.push(
this._blobContainerButton.onDidChangeCheckedState(async checked => {
if (checked) {
this.migrationStateModel._databaseBackup.networkContainerType = NetworkContainerType.BLOB_CONTAINER;
await utils.updateControlDisplay(this._dmsInfoContainer, false);
this.migrationStateModel.refreshDatabaseBackupPage = true;
}
}));
const flexContainer = this._view.modelBuilder.flexContainer()
.withItems([
selectLocationText,
this._blobContainerButton,
this._networkShareButton,
])
.withLayout({ flexFlow: 'column' })
.component();
return flexContainer;
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return;
}
const isSqlDbTarget = this.migrationStateModel.isSqlDbTarget;
const isNetworkShare = this.migrationStateModel.isBackupContainerNetworkShare;
this._subscription.value = this.migrationStateModel._targetSubscription.name;
this._location.value = await getLocationDisplayName(
this.migrationStateModel._targetServerInstance.location);
await utils.updateControlDisplay(
this._dmsInfoContainer,
this.migrationStateModel._targetType === MigrationTargetType.SQLDB ||
this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE);
await this.loadResourceGroupDropdown();
this.wizard.registerNavigationValidator((pageChangeInfo) => {
this.wizard.message = { text: '' };
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return true;
}
const isSqlDbTarget = this.migrationStateModel.isSqlDbTarget;
if (!isSqlDbTarget && !this._networkShareButton.checked && !this._blobContainerButton.checked) {
this.wizard.message = {
level: azdata.window.MessageLevel.Error,
text: constants.SERVICE_SELECTION_LOCATION_MESSAGE,
};
return false;
}
const state = this.migrationStateModel._sqlMigrationService?.properties?.integrationRuntimeState;
if (!this.migrationStateModel._sqlMigrationService) {
@@ -93,10 +216,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
};
return false;
}
if ((this.migrationStateModel._targetType === MigrationTargetType.SQLDB ||
this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE)
&& state !== 'Online') {
if ((isSqlDbTarget || isNetworkShare) && state !== 'Online') {
this.wizard.message = {
level: azdata.window.MessageLevel.Error,
text: constants.SERVICE_OFFLINE_ERROR
@@ -105,10 +225,43 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}
return true;
});
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return;
}
await utils.updateControlDisplay(this._modeContainer, !isSqlDbTarget);
this._onlineButton.enabled = !isSqlDbTarget;
if (isSqlDbTarget) {
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE;
this._offlineButton.checked = true;
}
this._originalMigrationMode = this.migrationStateModel._databaseBackup.migrationMode;
this._networkShareButton.checked = this.migrationStateModel.isBackupContainerNetworkShare;
this._blobContainerButton.checked = this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER;
await utils.updateControlDisplay(
this._radioButtonContainer,
!isSqlDbTarget);
this._subscription.value = this.migrationStateModel._targetSubscription.name;
this._location.value = await getLocationDisplayName(
this.migrationStateModel._targetServerInstance.location);
await utils.updateControlDisplay(
this._dmsInfoContainer,
isSqlDbTarget || isNetworkShare);
await this.loadResourceGroupDropdown();
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator((pageChangeInfo) => true);
this.wizard.registerNavigationValidator(pageChangeInfo => true);
this.wizard.message = { text: '' };
if (this._originalMigrationMode !== this.migrationStateModel._databaseBackup.migrationMode) {
this.migrationStateModel.refreshDatabaseBackupPage = true;
}
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
@@ -195,18 +348,20 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
async (value) => {
if (value && value !== 'undefined' && value !== constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR) {
this.wizard.message = { text: '' };
await utils.updateControlDisplay(
this._dmsInfoContainer,
this.migrationStateModel._targetType === MigrationTargetType.SQLDB ||
this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE);
const resourceGroupName = this.migrationStateModel._sqlMigrationServiceResourceGroup.name.toLowerCase();
const selectedDms = this.migrationStateModel._sqlMigrationServices.find(
dms => dms.name === value && dms.properties.resourceGroup.toLowerCase() === this.migrationStateModel._sqlMigrationServiceResourceGroup.name.toLowerCase());
dms => dms.name === value
&& dms.properties.resourceGroup.toLowerCase() === resourceGroupName);
if (selectedDms) {
this.migrationStateModel._sqlMigrationService = selectedDms;
await this.loadStatus();
}
await utils.updateControlDisplay(
this._dmsInfoContainer,
this.migrationStateModel.isSqlDbTarget ||
this.migrationStateModel.isBackupContainerNetworkShare);
} else {
this.migrationStateModel._sqlMigrationService = undefined;
await utils.updateControlDisplay(this._dmsInfoContainer, false);
@@ -276,7 +431,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this._disposables.push(
this._refreshButton.onDidClick(
async (e) => this.loadStatus()));
async (e) => await this.loadStatus()));
const connectionLabelContainer = this._view.modelBuilder.flexContainer()
.component();
@@ -398,7 +553,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this._dmsDropdown.values = utils.getAzureResourceDropdownValues(
this.migrationStateModel._sqlMigrationServices,
this.migrationStateModel._location,
this.migrationStateModel._sqlMigrationServiceResourceGroup.name,
this.migrationStateModel._sqlMigrationServiceResourceGroup?.name,
constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR);
utils.selectDefaultDropdownValue(

View File

@@ -1,132 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationMode, MigrationStateModel, MigrationTargetType, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import * as styles from '../constants/styles';
export class MigrationModePage extends MigrationWizardPage {
private _view!: azdata.ModelView;
private _onlineButton!: azdata.RadioButtonComponent;
private _offlineButton!: azdata.RadioButtonComponent;
private _originalMigrationMode!: MigrationMode;
private _disposables: vscode.Disposable[] = [];
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(
wizard,
azdata.window.createWizardPage(constants.DATABASE_BACKUP_MIGRATION_MODE_LABEL, 'MigrationModePage'),
migrationStateModel);
this.migrationStateModel._databaseBackup.migrationMode = this.migrationStateModel._databaseBackup.migrationMode || MigrationMode.ONLINE;
}
protected async registerContent(view: azdata.ModelView): Promise<void> {
this._view = view;
const pageDescription = {
title: '',
component: view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION,
CSSStyles: { ...styles.BODY_CSS, 'margin': '0' }
}).component()
};
const form = view.modelBuilder.formContainer()
.withFormItems([
pageDescription,
this.migrationModeContainer()])
.withProps({ CSSStyles: { 'padding-top': '0' } })
.component();
this._disposables.push(
this._view.onClosed(
e => this._disposables.forEach(
d => { try { d.dispose(); } catch { } })));
await view.initializeModel(form);
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return;
}
const isSqlDbTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLDB;
this._onlineButton.enabled = !isSqlDbTarget;
if (isSqlDbTarget) {
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE;
this._offlineButton.checked = true;
await this._offlineButton.focus();
}
this._originalMigrationMode = this.migrationStateModel._databaseBackup.migrationMode;
this.wizard.registerNavigationValidator((e) => true);
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (this._originalMigrationMode !== this.migrationStateModel._databaseBackup.migrationMode) {
this.migrationStateModel.refreshDatabaseBackupPage = true;
}
this.wizard.registerNavigationValidator((e) => true);
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
}
private migrationModeContainer(): azdata.FormComponent {
const buttonGroup = 'migrationMode';
this._onlineButton = this._view.modelBuilder.radioButton()
.withProps({
label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
name: buttonGroup,
checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.ONLINE,
CSSStyles: { ...styles.LABEL_CSS, },
}).component();
const onlineDescription = this._view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION,
CSSStyles: { ...styles.NOTE_CSS, 'margin-left': '20px' }
}).component();
this._disposables.push(
this._onlineButton.onDidChangeCheckedState(checked => {
if (checked) {
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.ONLINE;
}
}));
this._offlineButton = this._view.modelBuilder.radioButton()
.withProps({
label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
name: buttonGroup,
checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE,
CSSStyles: { ...styles.LABEL_CSS, 'margin-top': '12px' },
}).component();
const offlineDescription = this._view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_DESCRIPTION,
CSSStyles: { ...styles.NOTE_CSS, 'margin-left': '20px' }
}).component();
this._disposables.push(
this._offlineButton.onDidChangeCheckedState(checked => {
if (checked) {
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE;
}
}));
const flexContainer = this._view.modelBuilder.flexContainer()
.withItems([
this._onlineButton,
onlineDescription,
this._offlineButton,
offlineDescription]
).withLayout({ flexFlow: 'column' })
.component();
return { component: flexContainer };
}
}

View File

@@ -589,8 +589,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator(pageChangeInfo => true);
this.wizard.message = { text: '' };
this.wizard.registerNavigationValidator((pageChangeInfo) => true);
this.eventListener?.dispose();
}

View File

@@ -39,14 +39,11 @@ export class SqlSourceConfigurationPage extends MigrationWizardPage {
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator((pageChangeInfo) => {
return true;
});
this.wizard.registerNavigationValidator(pageChangeInfo => true);
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator((pageChangeInfo) => {
return true;
});
this.wizard.registerNavigationValidator(pageChangeInfo => true);
this.wizard.message = { text: '' };
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
@@ -59,9 +56,9 @@ export class SqlSourceConfigurationPage extends MigrationWizardPage {
const query = 'select SUSER_NAME()';
const results = await queryProvider.runQueryAndReturn(await (azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId)), query);
const username = results.rows[0][0].displayValue;
this.migrationStateModel._authenticationType = connectionProfile.authenticationType === 'SqlLogin'
this.migrationStateModel._authenticationType = connectionProfile.authenticationType === azdata.connection.AuthenticationType.SqlLogin
? MigrationSourceAuthenticationType.Sql
: connectionProfile.authenticationType === 'Integrated'
: connectionProfile.authenticationType === azdata.connection.AuthenticationType.Integrated
? MigrationSourceAuthenticationType.Integrated
: undefined!;

View File

@@ -40,11 +40,13 @@ export class SummaryPage extends MigrationWizardPage {
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator(pageChangeInfo => true);
const targetDatabaseSummary = new TargetDatabaseSummaryDialog(this.migrationStateModel);
const isSqlVmTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLVM;
const isSqlMiTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLMI;
const isSqlDbTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLDB;
const isNetworkShare = this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE;
const isSqlVmTarget = this.migrationStateModel.isSqlVmTarget;
const isSqlMiTarget = this.migrationStateModel.isSqlMiTarget;
const isSqlDbTarget = this.migrationStateModel.isSqlDbTarget;
const isNetworkShare = this.migrationStateModel.isBackupContainerNetworkShare;
const targetDatabaseHyperlink = this._view.modelBuilder.hyperlink()
.withProps({
@@ -132,8 +134,6 @@ export class SummaryPage extends MigrationWizardPage {
}
this._flexContainer.addItems([
await createHeadingTextComponent(
this._view,
constants.IR_PAGE_TITLE),
@@ -166,8 +166,9 @@ export class SummaryPage extends MigrationWizardPage {
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator(pageChangeInfo => true);
this.wizard.message = { text: '' };
this._flexContainer.clearItems();
this.wizard.registerNavigationValidator(async (pageChangeInfo) => true);
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {

View File

@@ -15,6 +15,7 @@ import * as utils from '../api/utils';
import { azureResource } from 'azurecore';
import { AzureSqlDatabaseServer, SqlVMServer } from '../api/azure';
import { collectTargetDatabaseInfo, TargetDatabaseInfo } from '../api/sqlUtils';
import { MigrationLocalStorage, MigrationServiceContext } from '../models/migrationLocalStorage';
export class TargetSelectionPage extends MigrationWizardPage {
private _view!: azdata.ModelView;
@@ -38,6 +39,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
private _testConectionButton!: azdata.ButtonComponent;
private _connectionResultsInfoBox!: azdata.InfoBoxComponent;
private _migrationTargetPlatform!: MigrationTargetType;
private _serviceContext!: MigrationServiceContext;
constructor(
wizard: azdata.window.Wizard,
@@ -51,6 +53,8 @@ export class TargetSelectionPage extends MigrationWizardPage {
protected async registerContent(view: azdata.ModelView): Promise<void> {
this._view = view;
this._serviceContext = await MigrationLocalStorage.getMigrationServiceContext();
this._pageDescription = this._view.modelBuilder.text()
.withProps({
value: constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(),
@@ -76,51 +80,6 @@ export class TargetSelectionPage extends MigrationWizardPage {
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return;
}
switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLMI:
this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_MI_CARD_TEXT);
this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
break;
case MigrationTargetType.SQLVM:
this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_VM_CARD_TEXT);
this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
break;
case MigrationTargetType.SQLDB:
this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_SQLDB_CARD_TEXT);
this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE;
this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE;
this._updateConnectionButtonState();
if (this.migrationStateModel._didUpdateDatabasesForMigration) {
await this._resetTargetMapping();
this.migrationStateModel._didUpdateDatabasesForMigration = false;
}
break;
}
const isSqlDbTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLDB;
await this._targetUserNameInputBox.updateProperty('required', isSqlDbTarget);
await this._targetPasswordInputBox.updateProperty('required', isSqlDbTarget);
await utils.updateControlDisplay(this._resourceAuthenticationContainer, isSqlDbTarget);
await this.populateAzureAccountsDropdown();
if (this._migrationTargetPlatform !== this.migrationStateModel._targetType) {
// if the user had previously selected values on this page, then went back to change the migration target platform
// and came back, forcibly reload the location/resource group/resource values since they will now be different
this._migrationTargetPlatform = this.migrationStateModel._targetType;
this._targetPasswordInputBox.value = '';
this.migrationStateModel._sqlMigrationServices = undefined!;
this.migrationStateModel._targetServerInstance = undefined!;
this.migrationStateModel._resourceGroup = undefined!;
this.migrationStateModel._location = undefined!;
await this.populateLocationDropdown();
}
this.wizard.registerNavigationValidator((pageChangeInfo) => {
this.wizard.message = { text: '' };
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
@@ -152,8 +111,8 @@ export class TargetSelectionPage extends MigrationWizardPage {
if (!targetMi || resourceDropdownValue === constants.NO_MANAGED_INSTANCE_FOUND) {
errors.push(constants.INVALID_MANAGED_INSTANCE_ERROR);
}
if (targetMi?.properties?.state !== 'Ready') {
errors.push(constants.MI_NOT_READY_ERROR(targetMi.name, targetMi.properties.state));
if (targetMi && targetMi.properties?.state !== 'Ready') {
errors.push(constants.MI_NOT_READY_ERROR(targetMi.name, targetMi.properties?.state));
}
break;
case MigrationTargetType.SQLVM:
@@ -168,7 +127,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
errors.push(constants.INVALID_SQL_DATABASE_ERROR);
}
// TODO: verify what state check is needed/possible?
if (targetSqlDB?.properties?.state !== 'Ready') {
if (targetSqlDB && targetSqlDB.properties?.state !== 'Ready') {
errors.push(constants.SQLDB_NOT_READY_ERROR(targetSqlDB.name, targetSqlDB.properties.state));
}
@@ -201,10 +160,70 @@ export class TargetSelectionPage extends MigrationWizardPage {
}
return true;
});
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
return;
}
switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLMI:
this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_MI_CARD_TEXT);
this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
break;
case MigrationTargetType.SQLVM:
this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_VM_CARD_TEXT);
this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
break;
case MigrationTargetType.SQLDB:
this._pageDescription.value = constants.AZURE_SQL_TARGET_PAGE_DESCRIPTION(constants.SKU_RECOMMENDATION_SQLDB_CARD_TEXT);
this._azureResourceDropdownLabel.value = constants.AZURE_SQL_DATABASE;
this._azureResourceDropdown.ariaLabel = constants.AZURE_SQL_DATABASE;
this._updateConnectionButtonState();
if (this.migrationStateModel._didUpdateDatabasesForMigration) {
await this._resetTargetMapping();
this.migrationStateModel._didUpdateDatabasesForMigration = false;
}
break;
}
const isSqlDbTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLDB;
await this._targetUserNameInputBox.updateProperties({ required: isSqlDbTarget });
await this._targetPasswordInputBox.updateProperties({ required: isSqlDbTarget });
await utils.updateControlDisplay(this._resourceAuthenticationContainer, isSqlDbTarget);
if (this._migrationTargetPlatform !== this.migrationStateModel._targetType) {
// if the user had previously selected values on this page, then went back to change the migration target platform
// and came back, forcibly reload the location/resource group/resource values since they will now be different
this._migrationTargetPlatform = this.migrationStateModel._targetType;
this._targetPasswordInputBox.value = '';
this.migrationStateModel._sqlMigrationServices = undefined!;
this.migrationStateModel._azureAccount = undefined!;
this.migrationStateModel._azureTenant = undefined!;
this.migrationStateModel._targetSubscription = undefined!;
this.migrationStateModel._location = undefined!;
this.migrationStateModel._resourceGroup = undefined!;
this.migrationStateModel._targetServerInstance = undefined!;
const clearDropDown = async (dropDown: azdata.DropDownComponent): Promise<void> => {
dropDown.values = [];
dropDown.value = undefined;
};
await clearDropDown(this._azureAccountsDropdown);
await clearDropDown(this._accountTenantDropdown);
await clearDropDown(this._azureSubscriptionDropdown);
await clearDropDown(this._azureLocationDropdown);
await clearDropDown(this._azureResourceGroupDropdown);
await clearDropDown(this._azureResourceDropdown);
}
await this.populateAzureAccountsDropdown();
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
this.wizard.registerNavigationValidator(async (pageChangeInfo) => true);
this.wizard.registerNavigationValidator(pageChangeInfo => true);
this.wizard.message = { text: '' };
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
@@ -231,7 +250,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
this._disposables.push(
this._azureAccountsDropdown.onValueChanged(async (value) => {
if (value && value !== 'undefined') {
const selectedAccount = this.migrationStateModel._azureAccounts.find(account => account.displayInfo.displayName === value);
const selectedAccount = this.migrationStateModel._azureAccounts?.find(account => account.displayInfo.displayName === value);
this.migrationStateModel._azureAccount = (selectedAccount)
? utils.deepClone(selectedAccount)!
: undefined!;
@@ -287,7 +306,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
* Replacing all the tenants in azure account with the tenant user has selected.
* All azure requests will only run on this tenant from now on
*/
const selectedTenant = this.migrationStateModel._accountTenants.find(tenant => tenant.displayName === value);
const selectedTenant = this.migrationStateModel._accountTenants?.find(tenant => tenant.displayName === value);
if (selectedTenant) {
this.migrationStateModel._azureTenant = utils.deepClone(selectedTenant)!;
this.migrationStateModel._azureAccount.properties.tenants = [this.migrationStateModel._azureTenant];
@@ -328,7 +347,8 @@ export class TargetSelectionPage extends MigrationWizardPage {
this._disposables.push(
this._azureSubscriptionDropdown.onValueChanged(async (value) => {
if (value && value !== 'undefined' && value !== constants.NO_SUBSCRIPTIONS_FOUND) {
const selectedSubscription = this.migrationStateModel._subscriptions.find(subscription => `${subscription.name} - ${subscription.id}` === value);
const selectedSubscription = this.migrationStateModel._subscriptions?.find(
subscription => `${subscription.name} - ${subscription.id}` === value);
this.migrationStateModel._targetSubscription = (selectedSubscription)
? utils.deepClone(selectedSubscription)!
: undefined!;
@@ -358,7 +378,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
this._disposables.push(
this._azureLocationDropdown.onValueChanged(async (value) => {
if (value && value !== 'undefined' && value !== constants.NO_LOCATION_FOUND) {
const selectedLocation = this.migrationStateModel._locations.find(location => location.displayName === value);
const selectedLocation = this.migrationStateModel._locations?.find(location => location.displayName === value);
this.migrationStateModel._location = (selectedLocation)
? utils.deepClone(selectedLocation)!
: undefined!;
@@ -585,7 +605,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
this._disposables.push(
this._azureResourceGroupDropdown.onValueChanged(async (value) => {
if (value && value !== 'undefined' && value !== constants.RESOURCE_GROUP_NOT_FOUND) {
const selectedResourceGroup = this.migrationStateModel._resourceGroups.find(rg => rg.name === value);
const selectedResourceGroup = this.migrationStateModel._resourceGroups?.find(rg => rg.name === value);
this.migrationStateModel._resourceGroup = (selectedResourceGroup)
? utils.deepClone(selectedResourceGroup)!
: undefined!;
@@ -622,15 +642,15 @@ export class TargetSelectionPage extends MigrationWizardPage {
switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLVM:
const selectedVm = this.migrationStateModel._targetSqlVirtualMachines.find(vm => vm.name === value);
const selectedVm = this.migrationStateModel._targetSqlVirtualMachines?.find(vm => vm.name === value);
if (selectedVm) {
this.migrationStateModel._targetServerInstance = utils.deepClone(selectedVm)! as SqlVMServer;
}
break;
case MigrationTargetType.SQLMI:
const selectedMi = this.migrationStateModel._targetManagedInstances
.find(mi => mi.name === value
|| constants.UNAVAILABLE_TARGET_PREFIX(mi.name) === value);
const selectedMi = this.migrationStateModel._targetManagedInstances?.find(
mi => mi.name === value ||
constants.UNAVAILABLE_TARGET_PREFIX(mi.name) === value);
if (selectedMi) {
this.migrationStateModel._targetServerInstance = utils.deepClone(selectedMi)! as azureResource.AzureSqlManagedInstance;
@@ -647,7 +667,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
}
break;
case MigrationTargetType.SQLDB:
const sqlDatabaseServer = this.migrationStateModel._targetSqlDatabaseServers.find(
const sqlDatabaseServer = this.migrationStateModel._targetSqlDatabaseServers?.find(
sqldb => sqldb.name === value || constants.UNAVAILABLE_TARGET_PREFIX(sqldb.name) === value);
if (sqlDatabaseServer) {
@@ -703,8 +723,8 @@ export class TargetSelectionPage extends MigrationWizardPage {
private _updateConnectionButtonState(): void {
const targetDatabaseServer = (this._azureResourceDropdown.value as azdata.CategoryValue)?.name ?? '';
const userName = this._targetUserNameInputBox.value ?? '';
const password = this._targetPasswordInputBox.value ?? '';
const userName = this.migrationStateModel._targetUserName ?? '';
const password = this.migrationStateModel._targetPassword ?? '';
this._testConectionButton.enabled = targetDatabaseServer.length > 0
&& userName.length > 0
&& password.length > 0;
@@ -764,12 +784,17 @@ export class TargetSelectionPage extends MigrationWizardPage {
try {
this._azureAccountsDropdown.loading = true;
this.migrationStateModel._azureAccounts = await utils.getAzureAccounts();
this._azureAccountsDropdown.values = await utils.getAzureAccountsDropdownValues(this.migrationStateModel._azureAccounts);
} finally {
this._azureAccountsDropdown.loading = false;
const accountId =
this.migrationStateModel._azureAccount?.displayInfo?.userId ??
this._serviceContext?.azureAccount?.displayInfo?.userId;
utils.selectDefaultDropdownValue(
this._azureAccountsDropdown,
this.migrationStateModel._azureAccount?.displayInfo?.userId,
accountId,
false);
}
}
@@ -777,17 +802,24 @@ export class TargetSelectionPage extends MigrationWizardPage {
private async populateTenantsDropdown(): Promise<void> {
try {
this._accountTenantDropdown.loading = true;
if (this.migrationStateModel._azureAccount && this.migrationStateModel._azureAccount.isStale === false && this.migrationStateModel._azureAccount.properties.tenants.length > 0) {
if (this.migrationStateModel._azureAccount?.isStale === false &&
this.migrationStateModel._azureAccount?.properties?.tenants?.length > 0) {
this.migrationStateModel._accountTenants = utils.getAzureTenants(this.migrationStateModel._azureAccount);
this._accountTenantDropdown.values = await utils.getAzureTenantsDropdownValues(this.migrationStateModel._accountTenants);
}
const tenantId =
this.migrationStateModel._azureTenant?.id ??
this._serviceContext?.tenant?.id;
utils.selectDefaultDropdownValue(
this._accountTenantDropdown,
this.migrationStateModel._azureTenant?.id,
tenantId,
true);
await this._accountTenantFlexContainer.updateCssStyles(this.migrationStateModel._azureAccount.properties.tenants.length > 1
? { 'display': 'inline' }
: { 'display': 'none' }
await this._accountTenantFlexContainer.updateCssStyles(
this.migrationStateModel._azureAccount?.properties?.tenants?.length > 1
? { 'display': 'inline' }
: { 'display': 'none' }
);
await this._azureAccountsDropdown.validate();
} finally {
@@ -804,9 +836,13 @@ export class TargetSelectionPage extends MigrationWizardPage {
console.log(e);
} finally {
this._azureSubscriptionDropdown.loading = false;
const subscriptionId =
this.migrationStateModel._targetSubscription?.id ??
this._serviceContext?.subscription?.id;
utils.selectDefaultDropdownValue(
this._azureSubscriptionDropdown,
this.migrationStateModel._targetSubscription?.id,
subscriptionId,
false);
}
}
@@ -848,9 +884,13 @@ export class TargetSelectionPage extends MigrationWizardPage {
console.log(e);
} finally {
this._azureLocationDropdown.loading = false;
const location =
this.migrationStateModel._location?.displayName ??
this._serviceContext?.location?.displayName;
utils.selectDefaultDropdownValue(
this._azureLocationDropdown,
this.migrationStateModel._location?.displayName,
location,
true);
}
}
@@ -882,6 +922,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
console.log(e);
} finally {
this._azureResourceGroupDropdown.loading = false;
utils.selectDefaultDropdownValue(
this._azureResourceGroupDropdown,
this.migrationStateModel._resourceGroup?.id,
@@ -916,9 +957,22 @@ export class TargetSelectionPage extends MigrationWizardPage {
}
} finally {
this._azureResourceDropdown.loading = false;
let targetName = '';
switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLMI:
targetName = (this.migrationStateModel._targetServerInstance as azureResource.AzureSqlManagedInstance)?.name;
break;
case MigrationTargetType.SQLVM:
targetName = (this.migrationStateModel._targetServerInstance as SqlVMServer)?.name;
break;
case MigrationTargetType.SQLDB:
targetName = (this.migrationStateModel._targetServerInstance as AzureSqlDatabaseServer)?.name;
break;
}
utils.selectDefaultDropdownValue(
this._azureResourceDropdown,
this.migrationStateModel._targetServerInstance?.name,
targetName,
true);
}
}
@@ -944,7 +998,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
.component();
this._disposables.push(
targetDatabaseDropDown.onValueChanged((targetDatabaseName: string) => {
const targetDatabaseInfo = targetDatabases.find(
const targetDatabaseInfo = targetDatabases?.find(
targetDb => targetDb.databaseName === targetDatabaseName);
this.migrationStateModel._sourceTargetMapping.set(
sourceDatabase,

View File

@@ -13,7 +13,6 @@ import { DatabaseBackupPage } from './databaseBackupPage';
import { TargetSelectionPage } from './targetSelectionPage';
import { IntergrationRuntimePage } from './integrationRuntimePage';
import { SummaryPage } from './summaryPage';
import { MigrationModePage } from './migrationModePage';
import { DatabaseSelectorPage } from './databaseSelectorPage';
import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError } from '../telemtery';
import * as styles from '../constants/styles';
@@ -48,23 +47,41 @@ export class WizardController {
this._wizardObject.generateScriptButton.enabled = false;
this._wizardObject.generateScriptButton.hidden = true;
const saveAndCloseButton = azdata.window.createButton(loc.SAVE_AND_CLOSE);
this._wizardObject.customButtons = [saveAndCloseButton];
this._wizardObject.nextButton.position = 'left';
this._wizardObject.nextButton.secondary = false;
this._wizardObject.doneButton.label = loc.START_MIGRATION_TEXT;
this._wizardObject.doneButton.position = 'left';
this._wizardObject.doneButton.secondary = false;
this._wizardObject.backButton.position = 'left';
this._wizardObject.backButton.secondary = true;
this._wizardObject.cancelButton.position = 'left';
this._wizardObject.cancelButton.secondary = true;
const saveAndCloseButton = azdata.window.createButton(
loc.SAVE_AND_CLOSE,
'right');
saveAndCloseButton.secondary = true;
const validateButton = azdata.window.createButton(
loc.RUN_VALIDATION,
'left');
validateButton.secondary = false;
validateButton.hidden = true;
this._wizardObject.customButtons = [validateButton, saveAndCloseButton];
const databaseSelectorPage = new DatabaseSelectorPage(this._wizardObject, stateModel);
const skuRecommendationPage = new SKURecommendationPage(this._wizardObject, stateModel);
const targetSelectionPage = new TargetSelectionPage(this._wizardObject, stateModel);
const migrationModePage = new MigrationModePage(this._wizardObject, stateModel);
const databaseBackupPage = new DatabaseBackupPage(this._wizardObject, stateModel);
const integrationRuntimePage = new IntergrationRuntimePage(this._wizardObject, stateModel);
const databaseBackupPage = new DatabaseBackupPage(this._wizardObject, stateModel);
const summaryPage = new SummaryPage(this._wizardObject, stateModel);
const pages: MigrationWizardPage[] = [
databaseSelectorPage,
skuRecommendationPage,
targetSelectionPage,
migrationModePage,
databaseBackupPage,
integrationRuntimePage,
databaseBackupPage,
summaryPage];
this._wizardObject.pages = pages.map(p => p.getwizardPage());
@@ -80,15 +97,15 @@ export class WizardController {
wizardSetupPromises.push(...pages.map(p => p.registerWizardContent()));
wizardSetupPromises.push(this._wizardObject.open());
if (this._model.retryMigration || this._model.resumeAssessment) {
if (this._model.savedInfo.closedPage >= Page.MigrationMode) {
if (this._model.savedInfo.closedPage >= Page.IntegrationRuntime) {
this._model.refreshDatabaseBackupPage = true;
}
// if the user selected network share and selected save & close afterwards, it should always return to the database backup page so that
// the user can input their password again
if (this._model.savedInfo.closedPage >= Page.DatabaseBackup &&
if (this._model.savedInfo.closedPage >= Page.IntegrationRuntime &&
this._model.savedInfo.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
wizardSetupPromises.push(this._wizardObject.setCurrentPage(Page.DatabaseBackup));
wizardSetupPromises.push(this._wizardObject.setCurrentPage(Page.IntegrationRuntime));
} else {
wizardSetupPromises.push(this._wizardObject.setCurrentPage(this._model.savedInfo.closedPage));
}
@@ -107,15 +124,7 @@ export class WizardController {
await pages[newPage]?.onPageEnter(pageChangeInfo);
}));
this._wizardObject.registerNavigationValidator(async validator => {
// const lastPage = validator.lastPage;
// const canLeave = await pages[lastPage]?.canLeave() ?? true;
// const canEnter = await pages[lastPage]?.canEnter() ?? true;
// return canEnter && canLeave;
return true;
});
this._wizardObject.registerNavigationValidator(async validator => true);
await Promise.all(wizardSetupPromises);
this._model.extensionContext.subscriptions.push(
@@ -146,8 +155,6 @@ export class WizardController {
{});
}));
this._wizardObject.doneButton.label = loc.START_MIGRATION_TEXT;
this._disposables.push(
this._wizardObject.doneButton.onClick(async (e) => {
try {