[Sql Migration] Stabilize save and close logic and fix related ux bugs (#18579)

* add loadSavedInfo function in stateMachine; only open wizard if didLoadSavedInfo

* * add loadSavedInfo function in stateMachine; only open wizard if didLoadSavedInfo
* replaced savedInfo.miggrationServiceId string with sqlMigrationServer object
* selectDatbasesFromList helper function to check previously selected dbs in dbSelectorPage and sqlDatabaseTree

* * remove savedInfo references from targetSelectionPage, migrationModePage
* add selectDefaultDropdownValue helper to stateMachine to handle unify savedInfo selection logic
* add updateDropdownLoadingStatus to targetSelectionPage
* check if values exist before making api calls in statemachine

* removed savedInfo references from databaseBackupPage, integrationRuntimePage

* databaseBackupPage - targetDatabaseNames, networkShares, blobs need to rely on savedInfo as user may update the list of migrationdbs during the retry/saveAndClose

* re-add serverAssessments to savedInfo; only getAssessments if it does not exist or needs to be updated; fix networkShare type savedInfo

* rename _assessmentDbs to _databasesForAssessment; _migrationDbs to _databasesForMigration

* load blobs/networkshares savedinfo; move selectDefaultDropdownValue to utils

* fix selectDefaultDropdownValue; refreshDatabaseBackupPage when user changes target subscription or location
This commit is contained in:
Rachel Kim
2022-03-04 16:00:44 -08:00
committed by GitHub
parent c9aa3e9f4b
commit 33259764f7
17 changed files with 621 additions and 643 deletions

View File

@@ -146,6 +146,15 @@ export function convertIsoTimeToLocalTime(isoTime: string): Date {
export type SupportedAutoRefreshIntervals = -1 | 15000 | 30000 | 60000 | 180000 | 300000;
export function selectDefaultDropdownValue(dropDown: DropDownComponent, value?: string, useDisplayName: boolean = true): void {
const selectedIndex = value ? findDropDownItemIndex(dropDown, value, useDisplayName) : -1;
if (selectedIndex > -1) {
selectDropDownIndex(dropDown, selectedIndex);
} else {
selectDropDownIndex(dropDown, 0);
}
}
export function selectDropDownIndex(dropDown: DropDownComponent, index: number): void {
if (index >= 0 && dropDown.values && index <= dropDown.values.length - 1) {
const value = dropDown.values[index];
@@ -153,10 +162,15 @@ export function selectDropDownIndex(dropDown: DropDownComponent, index: number):
}
}
export function findDropDownItemIndex(dropDown: DropDownComponent, value: string): number {
export function findDropDownItemIndex(dropDown: DropDownComponent, value: string, useDisplayName: boolean = true): number {
if (dropDown.values) {
if (useDisplayName) {
return dropDown.values.findIndex((v: any) =>
(v as CategoryValue)?.displayName?.toLowerCase() === value?.toLowerCase());
} else {
return dropDown.values.findIndex((v: any) =>
(v as CategoryValue)?.name?.toLowerCase() === value?.toLowerCase());
}
}
return -1;

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { MigrationContext, MigrationStatus } from '../models/migrationLocalStorage';
import { MigrationMode, MigrationTargetType } from '../models/stateMachine';
import * as loc from './strings';
@@ -48,3 +49,20 @@ export function canRetryMigration(status: string | undefined): boolean {
status === MigrationStatus.Succeeded ||
status === MigrationStatus.Canceled;
}
const TABLE_CHECKBOX_INDEX = 0;
const TABLE_DB_NAME_INDEX = 1;
export function selectDatabasesFromList(selectedDbs: string[], databaseTableValues: azdata.DeclarativeTableCellValue[][]): azdata.DeclarativeTableCellValue[][] {
const sourceDatabaseNames = selectedDbs?.map(dbName => dbName.toLocaleLowerCase()) || [];
if (sourceDatabaseNames?.length > 0) {
for (let i in databaseTableValues) {
const row = databaseTableValues[i];
const dbName = (row[TABLE_DB_NAME_INDEX].value as string).toLocaleLowerCase();
if (sourceDatabaseNames.indexOf(dbName) > -1) {
row[TABLE_CHECKBOX_INDEX].value = true;
}
}
}
return databaseTableValues;
}

View File

@@ -17,10 +17,13 @@ export function WIZARD_TITLE(instanceName: string): string {
}
// //#endregion
// Resume Migration Dialog
// Save and close
export const SAVE_AND_CLOSE = localize('sql.migration.save.close', "Save and close");
export const SAVE_AND_CLOSE_POPUP = localize('sql.migration.save.close.popup', "Configuration saved. Performance data collection will remain running in the background. You can stop the collection when you want to.");
export const RESUME_TITLE = localize('sql.migration.resume.title', "Run migration workflow again");
export const START_MIGRATION = localize('sql.migration.resume.start', "Start a new session");
export const CONTINUE_MIGRATION = localize('sql.migration.resume.continue', "Resume previously saved session");
export const START_NEW_SESSION = localize('sql.migration.start.session', "Start a new session");
export const RESUME_SESSION = localize('sql.migration.resume.session', "Resume previously saved session");
export const OPEN_SAVED_INFO_ERROR = localize("sql.migration.invalid.savedInfo", 'Cannot retrieve saved session. Try again by selecting new session.');
// Databases for assessment
export const DATABASE_FOR_ASSESSMENT_PAGE_TITLE = localize('sql.migration.database.assessment.title', "Databases for assessment");
@@ -267,6 +270,8 @@ export const ACCOUNTS_SELECTION_PAGE_TITLE = localize('sql.migration.wizard.acco
export const ACCOUNTS_SELECTION_PAGE_DESCRIPTION = localize('sql.migration.wizard.account.description', "Select an Azure account linked to Azure Data Studio, or link one now.");
export const ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR = localize('sql.migration.wizard.account.noAccount.error', "Add a linked account and then try again.");
export const ACCOUNT_LINK_BUTTON_LABEL = localize('sql.migration.wizard.account.add.button.label', "Link account");
export const INVALID_ACCOUNT_ERROR = localize('sql.migration.invalid.account.error', "To continue, select a valid Azure account.");
export function accountLinkedMessage(count: number): string {
return count === 1 ? localize('sql.migration.wizard.account.count.single.message', '{0} account linked', count) : localize('sql.migration.wizard.account.count.multiple.message', '{0} accounts linked', count);
}
@@ -665,8 +670,6 @@ export const SQL_MIGRATION_SERVICE_DETAILS_AUTH_KEYS_TITLE = localize('sql.migra
export const SQL_MIGRATION_SERVICE_DETAILS_STATUS_UNAVAILABLE = localize('sql.migration.service.details.status.unavailable', "-- unavailable --");
//Source Credentials page.
export const SAVE_AND_CLOSE = localize('sql.migration.save.close', "Save and close");
export const SAVE_AND_CLOSE_POPUP = localize('sql.migration.save.close.popup', "Configuration saved. Performance data collection will remain running in the background. You can stop the collection when you want to.");
export const SOURCE_CONFIGURATION = localize('sql.migration.source.configuration', "Source configuration");
export const SOURCE_CREDENTIALS = localize('sql.migration.source.credentials', "Source credentials");
export const ENTER_YOUR_SQL_CREDS = localize('sql.migration.enter.your.sql.cred', "Enter the credentials for the source SQL Server instance. These credentials will be used while migrating databases to Azure SQL.");

View File

@@ -5,7 +5,7 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { MigrationStateModel, MigrationTargetType, Page } from '../../models/stateMachine';
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine';
import { SqlDatabaseTree } from './sqlDatabasesTree';
import { SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
@@ -32,9 +32,6 @@ export class AssessmentResultsDialog {
constructor(public ownerUri: string, public model: MigrationStateModel, public title: string, private _skuRecommendationPage: SKURecommendationPage, private _targetType: MigrationTargetType) {
this._model = model;
if (this._model.resumeAssessment && this._model.savedInfo.closedPage >= Page.DatabaseBackup) {
this._model._databaseAssessment = <string[]>this._model.savedInfo.databaseAssessment;
}
this._tree = new SqlDatabaseTree(this._model, this._targetType);
}
@@ -87,16 +84,31 @@ export class AssessmentResultsDialog {
}
protected async execute() {
if (this._targetType === MigrationTargetType.SQLVM) {
this._model._vmDbs = this._tree.selectedDbs();
} else {
this._model._miDbs = this._tree.selectedDbs();
const selectedDbs = this._tree.selectedDbs();
switch (this._targetType) {
case MigrationTargetType.SQLMI: {
this.didUpdateDatabasesForMigration(this._model._miDbs, selectedDbs);
this._model._miDbs = selectedDbs;
break;
}
case MigrationTargetType.SQLVM: {
this.didUpdateDatabasesForMigration(this._model._vmDbs, selectedDbs);
this._model._vmDbs = selectedDbs;
break;
}
}
await this._skuRecommendationPage.refreshCardText();
this.model.refreshDatabaseBackupPage = true;
this._isOpen = false;
}
private didUpdateDatabasesForMigration(priorDbs: string[], selectedDbs: string[]) {
this._model._didUpdateDatabasesForMigration = selectedDbs.length === 0
|| selectedDbs.length !== priorDbs.length
|| priorDbs.some(db => selectedDbs.indexOf(db) < 0);
}
protected async cancel() {
this._isOpen = false;
}

View File

@@ -50,6 +50,16 @@ export class SavedAssessmentDialog {
reject(ex);
}
});
dialog.registerCloseValidator(async () => {
if (this.stateModel.resumeAssessment) {
if (!this.stateModel.loadSavedInfo()) {
void vscode.window.showInformationMessage(constants.OPEN_SAVED_INFO_ERROR);
return false;
}
}
return true;
});
});
}
@@ -67,14 +77,8 @@ export class SavedAssessmentDialog {
}
protected async execute() {
if (this.stateModel.resumeAssessment) {
const wizardController = new WizardController(this.context, this.stateModel);
await wizardController.openWizard(this.stateModel.sourceConnectionId);
} else {
// normal flow
const wizardController = new WizardController(this.context, this.stateModel);
await wizardController.openWizard(this.stateModel.sourceConnectionId);
}
this._isOpen = false;
}
@@ -90,7 +94,7 @@ export class SavedAssessmentDialog {
const buttonGroup = 'resumeMigration';
const radioStart = view.modelBuilder.radioButton().withProps({
label: constants.START_MIGRATION,
label: constants.START_NEW_SESSION,
name: buttonGroup,
CSSStyles: {
...styles.BODY_CSS,
@@ -105,7 +109,7 @@ export class SavedAssessmentDialog {
}
});
const radioContinue = view.modelBuilder.radioButton().withProps({
label: constants.CONTINUE_MIGRATION,
label: constants.RESUME_SESSION,
name: buttonGroup,
CSSStyles: {
...styles.BODY_CSS,
@@ -122,11 +126,9 @@ export class SavedAssessmentDialog {
const flex = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'column',
height: '100%',
width: '100%',
}).withProps({
CSSStyles: {
'margin': '20px 15px',
'padding': '20px 15px',
}
}).component();
flex.addItem(radioStart, { flex: '0 0 auto' });
@@ -134,5 +136,4 @@ export class SavedAssessmentDialog {
return flex;
}
}

View File

@@ -5,12 +5,13 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { SqlMigrationAssessmentResultItem, SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
import { MigrationStateModel, MigrationTargetType, Page } from '../../models/stateMachine';
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine';
import * as constants from '../../constants/strings';
import { debounce } from '../../api/utils';
import { IconPath, IconPathHelper } from '../../constants/iconPathHelper';
import * as styles from '../../constants/styles';
import { EOL } from 'os';
import { selectDatabasesFromList } from '../../constants/helper';
const styleLeft: azdata.CssStyles = {
'border': 'none',
@@ -142,7 +143,7 @@ export class SqlDatabaseTree {
...styles.BOLD_NOTE_CSS,
'margin': '0px 15px 0px 15px'
},
value: constants.DATABASES(0, this._model._databaseAssessment?.length)
value: constants.DATABASES(0, this._model._databasesForAssessment?.length)
}).component();
return this._databaseCount;
}
@@ -881,7 +882,7 @@ export class SqlDatabaseTree {
public async initialize(): Promise<void> {
let instanceTableValues: azdata.DeclarativeTableCellValue[][] = [];
this._databaseTableValues = [];
this._dbNames = this._model._databaseAssessment;
this._dbNames = this._model._databasesForAssessment;
const selectedDbs = (this._targetType === MigrationTargetType.SQLVM) ? this._model._vmDbs : this._model._miDbs;
this._serverName = (await this._model.getSourceConnectionProfile()).serverName;
@@ -959,25 +960,16 @@ export class SqlDatabaseTree {
});
}
await this._instanceTable.setDataValues(instanceTableValues);
if (this._model.resumeAssessment && this._model.savedInfo.closedPage >= Page.SKURecommendation && this._targetType === this._model.savedInfo.migrationTargetType) {
await this._databaseTable.setDataValues(this._model.savedInfo.migrationDatabases);
} else {
if (this._model.retryMigration && this._targetType === this._model.savedInfo.migrationTargetType) {
const sourceDatabaseName = this._model.savedInfo.databaseList[0];
const sourceDatabaseIndex = this._dbNames.indexOf(sourceDatabaseName);
this._databaseTableValues[sourceDatabaseIndex][0].value = true;
}
this._databaseTableValues = selectDatabasesFromList(this._model._databasesForMigration, this._databaseTableValues);
await this._databaseTable.setDataValues(this._databaseTableValues);
await this.updateValuesOnSelection();
}
}
private async updateValuesOnSelection() {
await this._databaseCount.updateProperties({
'value': constants.DATABASES(this.selectedDbs()?.length, this._model._databaseAssessment?.length)
'value': constants.DATABASES(this.selectedDbs()?.length, this._model._databasesForAssessment?.length)
});
this._model._databaseSelection = <azdata.DeclarativeTableCellValue[][]>this._databaseTable.dataValues;
}
// undo when bug #16445 is fixed

View File

@@ -12,6 +12,7 @@ import { MigrationMode, MigrationStateModel, NetworkContainerType, SavedInfo } f
import { MigrationContext } from '../../models/migrationLocalStorage';
import { WizardController } from '../../wizard/wizardController';
import { getMigrationModeEnum, getMigrationTargetTypeEnum } from '../../constants/helper';
import * as constants from '../../constants/strings';
export class RetryMigrationDialog {
private _context: vscode.ExtensionContext;
@@ -30,21 +31,18 @@ export class RetryMigrationDialog {
savedInfo = {
closedPage: 0,
// AzureAccount
azureAccount: migration.azureAccount,
azureTenant: migration.azureAccount.properties.tenants[0],
// DatabaseSelector
selectedDatabases: [],
databaseAssessment: [sourceDatabaseName],
// SKURecommendation
databaseAssessment: [],
databaseList: [sourceDatabaseName],
migrationDatabases: [],
serverAssessment: null,
skuRecommendation: null,
migrationTargetType: getMigrationTargetTypeEnum(migration)!,
// TargetSelection
azureAccount: migration.azureAccount,
azureTenant: migration.azureAccount.properties.tenants[0],
subscription: migration.subscription,
location: location,
resourceGroup: {
@@ -58,14 +56,13 @@ export class RetryMigrationDialog {
migrationMode: getMigrationModeEnum(migration),
// DatabaseBackup
targetSubscription: migration.subscription,
targetDatabaseNames: [migration.migrationContext.name],
networkContainerType: null,
networkShares: [],
blobs: [],
// Integration Runtime
migrationServiceId: migration.migrationContext.properties.migrationService,
sqlMigrationService: migration.controller,
};
const getStorageAccountResourceGroup = (storageAccountResourceId: string) => {
@@ -151,7 +148,11 @@ export class RetryMigrationDialog {
const api = (await vscode.extensions.getExtension(mssql.extension.name)?.activate()) as mssql.IExtension;
const stateModel = this.createMigrationStateModel(this._migration, connectionId, serverName, api, location!);
if (stateModel.loadSavedInfo()) {
const wizardController = new WizardController(this._context, stateModel);
await wizardController.openWizard(stateModel.sourceConnectionId);
} else {
void vscode.window.showInformationMessage(constants.MIGRATION_CANNOT_RETRY);
}
}
}

View File

@@ -35,7 +35,7 @@ export class TargetDatabaseSummaryDialog {
this._view = view;
const databaseCount = this._view.modelBuilder.text().withProps({
value: constants.COUNT_DATABASES(this._model._migrationDbs.length),
value: constants.COUNT_DATABASES(this._model._databasesForMigration.length),
CSSStyles: {
...styles.BODY_CSS,
'margin-bottom': '20px'
@@ -132,7 +132,7 @@ export class TargetDatabaseSummaryDialog {
const tableRows: azdata.DeclarativeTableCellValue[][] = [];
this._model._migrationDbs.forEach((db, index) => {
this._model._databasesForMigration.forEach((db, index) => {
const tableRow: azdata.DeclarativeTableCellValue[] = [];
tableRow.push({
value: db

View File

@@ -118,25 +118,22 @@ export interface StateChangeEvent {
export interface SavedInfo {
closedPage: number;
serverAssessment: ServerAssessment | null;
databaseAssessment: string[];
databaseList: string[];
migrationTargetType: MigrationTargetType | null;
azureAccount: azdata.Account | null;
azureTenant: azurecore.Tenant | null;
selectedDatabases: azdata.DeclarativeTableCellValue[][];
migrationTargetType: MigrationTargetType | null;
migrationDatabases: azdata.DeclarativeTableCellValue[][];
databaseList: string[];
subscription: azureResource.AzureResourceSubscription | null;
location: azureResource.AzureLocation | null;
resourceGroup: azureResource.AzureResourceResourceGroup | null;
targetServerInstance: azureResource.AzureSqlManagedInstance | SqlVMServer | null;
migrationMode: MigrationMode | null;
databaseAssessment: string[] | null;
networkContainerType: NetworkContainerType | null;
networkShares: NetworkShare[];
targetSubscription: azureResource.AzureResourceSubscription | null;
blobs: Blob[];
targetDatabaseNames: string[];
migrationServiceId: string | null;
sqlMigrationService: SqlMigrationService | undefined;
serverAssessment: ServerAssessment | null;
skuRecommendation: SkuRecommendationSavedInfo | null;
}
@@ -160,10 +157,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _authenticationType!: MigrationSourceAuthenticationType;
public _sqlServerUsername!: string;
public _sqlServerPassword!: string;
public _databaseAssessment!: string[];
public _subscriptions!: azureResource.AzureResourceSubscription[];
public _targetSubscription!: azureResource.AzureResourceSubscription;
public _locations!: azureResource.AzureLocation[];
public _location!: azureResource.AzureLocation;
@@ -173,11 +168,11 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _targetSqlVirtualMachines!: SqlVMServer[];
public _targetServerInstance!: SqlManagedInstance | SqlVMServer;
public _databaseBackup!: DatabaseBackupModel;
public _migrationDbs: string[] = [];
public _storageAccounts!: StorageAccount[];
public _fileShares!: azureResource.FileShare[];
public _blobContainers!: azureResource.BlobContainer[];
public _lastFileNames!: azureResource.Blob[];
public _sourceDatabaseNames!: string[];
public _targetDatabaseNames!: string[];
public _sqlMigrationServiceResourceGroup!: string;
@@ -189,11 +184,19 @@ export class MigrationStateModel implements Model, vscode.Disposable {
private _currentState: State;
private _gatheringInformationError: string | undefined;
public _databasesForAssessment!: string[];
public _assessmentResults!: ServerAssessment;
public _assessedDatabaseList!: string[];
public _runAssessments: boolean = true;
private _assessmentApiResponse!: mssql.AssessmentResult;
public mementoString: string;
public _databasesForMigration: string[] = [];
public _didUpdateDatabasesForMigration: boolean = false;
public _vmDbs: string[] = [];
public _miDbs: string[] = [];
public _targetType!: MigrationTargetType;
public _skuRecommendationResults!: SkuRecommendation;
public _skuRecommendationPerformanceDataSource!: PerformanceDataSourceOptions;
private _skuRecommendationApiResponse!: mssql.SkuRecommendationResult;
@@ -225,12 +228,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _skuTargetPercentile!: number;
public _skuEnablePreview!: boolean;
public _vmDbs: string[] = [];
public _miDbs: string[] = [];
public _targetType!: MigrationTargetType;
public refreshDatabaseBackupPage!: boolean;
public _databaseSelection!: azdata.DeclarativeTableCellValue[][];
public retryMigration!: boolean;
public resumeAssessment!: boolean;
public savedInfo!: SavedInfo;
@@ -244,7 +242,6 @@ export class MigrationStateModel implements Model, vscode.Disposable {
'model'
];
public serverName!: string;
public databaseSelectorTableValues!: azdata.DeclarativeTableCellValue[][];
constructor(
public extensionContext: vscode.ExtensionContext,
@@ -255,6 +252,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._databaseBackup = {} as DatabaseBackupModel;
this._databaseBackup.networkShares = [];
this._databaseBackup.blobs = [];
this._targetDatabaseNames = [];
this.mementoString = 'sqlMigration.assessmentResults';
this._skuScalingFactor = 100;
@@ -282,7 +280,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
}
public hasRecommendedDatabaseListChanged(): boolean {
const oldDbList = this._skuRecommendationRecommendedDatabaseList;
const newDbList = this._databaseAssessment;
const newDbList = this._databasesForAssessment;
if (!oldDbList || !newDbList) {
return false;
@@ -295,8 +293,10 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getDatabaseAssessments(targetType: MigrationTargetType): Promise<ServerAssessment> {
const ownerUri = await azdata.connection.getUriForConnection(this.sourceConnectionId);
try {
const response = (await this.migrationService.getAssessments(ownerUri, this._databaseAssessment))!;
const response = (await this.migrationService.getAssessments(ownerUri, this._databasesForAssessment))!;
this._assessmentApiResponse = response;
this._assessedDatabaseList = this._databasesForAssessment.slice();
if (response?.assessmentResult) {
response.assessmentResult.items = response.assessmentResult.items?.filter(
issue => issue.appliesToMigrationTargetPlatform === targetType);
@@ -318,7 +318,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
} else {
this._assessmentResults = {
issues: [],
databaseAssessments: this._databaseAssessment?.map(database => {
databaseAssessments: this._databasesForAssessment?.map(database => {
return {
name: database,
issues: [],
@@ -332,7 +332,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
} catch (error) {
this._assessmentResults = {
issues: [],
databaseAssessments: this._databaseAssessment?.map(database => {
databaseAssessments: this._databasesForAssessment?.map(database => {
return {
name: database,
issues: [],
@@ -364,11 +364,11 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._defaultDataPointStartTime,
this._defaultDataPointEndTime,
this._skuEnablePreview,
this._databaseAssessment))!;
this._databasesForAssessment))!;
this._skuRecommendationApiResponse = response;
// clone list of databases currently being assessed and store them, so that if the user ever changes the list we can refresh new recommendations
this._skuRecommendationRecommendedDatabaseList = this._databaseAssessment.slice();
this._skuRecommendationRecommendedDatabaseList = this._databasesForAssessment.slice();
if (response?.sqlDbRecommendationResults || response?.sqlMiRecommendationResults || response?.sqlVmRecommendationResults) {
this._skuRecommendationResults = {
@@ -862,9 +862,12 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getSubscriptionsDropdownValues(): Promise<azdata.CategoryValue[]> {
let subscriptionsValues: azdata.CategoryValue[] = [];
try {
if (!this._subscriptions) {
if (this._azureAccount) {
this._subscriptions = await getSubscriptions(this._azureAccount);
} else {
this._subscriptions = [];
}
this._subscriptions.forEach((subscription) => {
subscriptionsValues.push({
name: subscription.id,
@@ -900,7 +903,12 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getAzureLocationDropdownValues(subscription: azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let locationValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription) {
this._locations = await getLocations(this._azureAccount, subscription);
} else {
this._locations = [];
}
this._locations.forEach((loc) => {
locationValues.push({
name: loc.name,
@@ -911,7 +919,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
if (locationValues.length === 0) {
locationValues = [
{
displayName: constants.INVALID_LOCATION_ERROR,
displayName: constants.NO_LOCATION_FOUND,
name: ''
}
];
@@ -920,7 +928,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
console.log(e);
locationValues = [
{
displayName: constants.INVALID_LOCATION_ERROR,
displayName: constants.NO_LOCATION_FOUND,
name: ''
}
];
@@ -940,7 +948,12 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getAzureResourceGroupDropdownValues(subscription: azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let resourceGroupValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription) {
this._resourceGroups = await getResourceGroups(this._azureAccount, subscription);
} else {
this._resourceGroups = [];
}
this._resourceGroups.forEach((rg) => {
resourceGroupValues.push({
name: rg.id,
@@ -974,16 +987,18 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getManagedInstanceValues(subscription: azureResource.AzureResourceSubscription, location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> {
let managedInstanceValues: azdata.CategoryValue[] = [];
if (!this._azureAccount || !subscription) {
return managedInstanceValues;
}
try {
if (this._azureAccount && subscription && location && resourceGroup) {
this._targetManagedInstances = (await getAvailableManagedInstanceProducts(this._azureAccount, subscription)).filter((mi) => {
if (mi.location.toLowerCase() === location?.name.toLowerCase() && mi.resourceGroup?.toLowerCase() === resourceGroup?.name.toLowerCase()) {
return true;
}
return false;
});
} else {
this._targetManagedInstances = [];
}
this._targetManagedInstances.forEach((managedInstance) => {
managedInstanceValues.push({
name: managedInstance.id,
@@ -1024,7 +1039,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getSqlVirtualMachineValues(subscription: azureResource.AzureResourceSubscription, location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> {
let virtualMachineValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && resourceGroup) {
if (this._azureAccount && subscription && location && resourceGroup) {
this._targetSqlVirtualMachines = (await getAvailableSqlVMs(this._azureAccount, subscription, resourceGroup)).filter((virtualMachine) => {
if (virtualMachine?.location?.toLowerCase() === location?.name?.toLowerCase()) {
if (virtualMachine.properties.sqlImageOffer) {
@@ -1074,10 +1089,15 @@ export class MigrationStateModel implements Model, vscode.Disposable {
return storageAccountValues;
}
try {
if (this._azureAccount && subscription && resourceGroup) {
const storageAccount = (await getAvailableStorageAccounts(this._azureAccount, subscription));
this._storageAccounts = storageAccount.filter(sa => {
return sa.location.toLowerCase() === this._targetServerInstance.location.toLowerCase() && sa.resourceGroup?.toLowerCase() === resourceGroup.name.toLowerCase();
});
} else {
this._storageAccounts = [];
}
this._storageAccounts.forEach((storageAccount) => {
storageAccountValues.push({
name: storageAccount.id,
@@ -1112,7 +1132,12 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getFileShareValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise<azdata.CategoryValue[]> {
let fileShareValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && storageAccount) {
this._fileShares = await getFileShares(this._azureAccount, subscription, storageAccount);
} else {
this._fileShares = [];
}
this._fileShares.forEach((fileShare) => {
fileShareValues.push({
name: fileShare.id,
@@ -1146,17 +1171,13 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getBlobContainerValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise<azdata.CategoryValue[]> {
let blobContainerValues: azdata.CategoryValue[] = [];
if (!this._azureAccount || !subscription || !storageAccount) {
blobContainerValues = [
{
displayName: constants.NO_BLOBCONTAINERS_FOUND,
name: ''
}
];
return blobContainerValues;
}
try {
if (this._azureAccount && subscription && storageAccount) {
this._blobContainers = await getBlobContainers(this._azureAccount, subscription, storageAccount);
} else {
this._blobContainers = [];
}
this._blobContainers.forEach((blobContainer) => {
blobContainerValues.push({
name: blobContainer.id,
@@ -1191,21 +1212,26 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getBlobLastBackupFileNameValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount, blobContainer: azureResource.BlobContainer): Promise<azdata.CategoryValue[]> {
let blobLastBackupFileValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && storageAccount && blobContainer) {
this._lastFileNames = await getBlobs(this._azureAccount, subscription, storageAccount, blobContainer.name);
if (this._lastFileNames.length === 0) {
blobLastBackupFileValues = [
{
displayName: constants.NO_BLOBFILES_FOUND,
name: ''
}
];
} else {
this._lastFileNames = [];
}
this._lastFileNames.forEach((blob) => {
blobLastBackupFileValues.push({
name: blob.name,
displayName: `${blob.name}`,
});
});
if (blobLastBackupFileValues.length === 0) {
blobLastBackupFileValues = [
{
displayName: constants.NO_BLOBFILES_FOUND,
name: ''
}
];
}
} catch (e) {
console.log(e);
@@ -1220,13 +1246,18 @@ export class MigrationStateModel implements Model, vscode.Disposable {
}
public getBlobLastBackupFileName(index: number): string {
return this._lastFileNames[index].name;
return this._lastFileNames[index]?.name;
}
public async getSqlMigrationServiceValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance, resourceGroupName: string): Promise<azdata.CategoryValue[]> {
public async getSqlMigrationServiceValues(subscription: azureResource.AzureResourceSubscription, resourceGroupName: string): Promise<azdata.CategoryValue[]> {
let sqlMigrationServiceValues: azdata.CategoryValue[] = [];
try {
if (this._azureAccount && subscription && resourceGroupName && this._targetServerInstance) {
this._sqlMigrationServices = (await getSqlMigrationServices(this._azureAccount, subscription, resourceGroupName?.toLowerCase(), this._sessionId)).filter(sms => sms.location.toLowerCase() === this._targetServerInstance.location.toLowerCase());
} else {
this._sqlMigrationServices = [];
}
this._sqlMigrationServices.forEach((sqlMigrationService) => {
sqlMigrationServiceValues.push({
name: sqlMigrationService.id,
@@ -1289,7 +1320,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
}
};
for (let i = 0; i < this._migrationDbs.length; i++) {
for (let i = 0; i < this._databasesForMigration.length; i++) {
try {
switch (this._databaseBackup.networkContainerType) {
case NetworkContainerType.BLOB_CONTAINER:
@@ -1327,7 +1358,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
};
break;
}
requestBody.properties.sourceDatabaseName = this._migrationDbs[i];
requestBody.properties.sourceDatabaseName = this._databasesForMigration[i];
const response = await startDatabaseMigration(
this._azureAccount,
this._targetSubscription,
@@ -1337,7 +1368,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
requestBody,
this._sessionId
);
response.databaseMigration.properties.sourceDatabaseName = this._migrationDbs[i];
response.databaseMigration.properties.sourceDatabaseName = this._databasesForMigration[i];
response.databaseMigration.properties.backupConfiguration = requestBody.properties.backupConfiguration!;
response.databaseMigration.properties.offlineConfiguration = requestBody.properties.offlineConfiguration!;
@@ -1359,7 +1390,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
'location': this._targetServerInstance.location,
'targetType': this._targetType,
'hashedServerName': hashString(this._assessmentApiResponse?.assessmentResult?.name),
'hashedDatabaseName': hashString(this._migrationDbs[i]),
'hashedDatabaseName': hashString(this._databasesForMigration[i]),
'migrationMode': isOfflineMigration ? 'offline' : 'online',
'migrationStartTime': new Date().toString(),
'targetDatabaseName': this._targetDatabaseNames[i],
@@ -1382,7 +1413,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
response.asyncUrl,
this._sessionId
);
void vscode.window.showInformationMessage(localize("sql.migration.starting.migration.message", 'Starting migration for database {0} to {1} - {2}', this._migrationDbs[i], this._targetServerInstance.name, this._targetDatabaseNames[i]));
void vscode.window.showInformationMessage(localize("sql.migration.starting.migration.message", 'Starting migration for database {0} to {1} - {2}', this._databasesForMigration[i], this._targetServerInstance.name, this._targetDatabaseNames[i]));
}
} catch (e) {
void vscode.window.showErrorMessage(
@@ -1404,37 +1435,33 @@ export class MigrationStateModel implements Model, vscode.Disposable {
let saveInfo: SavedInfo;
saveInfo = {
closedPage: currentPage,
serverAssessment: null,
databaseAssessment: [],
databaseList: [],
migrationTargetType: null,
azureAccount: null,
azureTenant: null,
selectedDatabases: [],
migrationTargetType: null,
migrationDatabases: [],
databaseList: [],
subscription: null,
location: null,
resourceGroup: null,
targetServerInstance: null,
migrationMode: null,
databaseAssessment: null,
networkContainerType: null,
networkShares: [],
targetSubscription: null,
blobs: [],
targetDatabaseNames: [],
migrationServiceId: null,
sqlMigrationService: undefined,
serverAssessment: null,
skuRecommendation: null,
};
switch (currentPage) {
case Page.Summary:
case Page.IntegrationRuntime:
saveInfo.migrationServiceId = this._sqlMigrationService?.id!;
saveInfo.sqlMigrationService = this._sqlMigrationService;
case Page.DatabaseBackup:
saveInfo.networkContainerType = this._databaseBackup.networkContainerType;
saveInfo.networkShares = this._databaseBackup.networkShares;
saveInfo.targetSubscription = this._databaseBackup.subscription;
saveInfo.blobs = this._databaseBackup.blobs;
saveInfo.targetDatabaseNames = this._targetDatabaseNames;
@@ -1451,10 +1478,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
case Page.SKURecommendation:
saveInfo.migrationTargetType = this._targetType;
saveInfo.databaseAssessment = this._databaseAssessment;
saveInfo.databaseList = this._databasesForMigration;
saveInfo.serverAssessment = this._assessmentResults;
saveInfo.migrationDatabases = this._databaseSelection;
saveInfo.databaseList = this._migrationDbs;
if (this._skuRecommendationPerformanceDataSource) {
let skuRecommendation: SkuRecommendationSavedInfo = {
@@ -1470,10 +1495,68 @@ export class MigrationStateModel implements Model, vscode.Disposable {
}
case Page.DatabaseSelector:
saveInfo.selectedDatabases = this.databaseSelectorTableValues;
saveInfo.databaseAssessment = this._databasesForAssessment;
await this.extensionContext.globalState.update(`${this.mementoString}.${serverName}`, saveInfo);
}
}
public loadSavedInfo(): Boolean {
try {
this._targetType = this.savedInfo.migrationTargetType || undefined!;
this._databasesForAssessment = this.savedInfo.databaseAssessment;
this._databasesForMigration = this.savedInfo.databaseList;
this._didUpdateDatabasesForMigration = true;
switch (this._targetType) {
case MigrationTargetType.SQLMI:
this._miDbs = this._databasesForMigration;
break;
case MigrationTargetType.SQLVM:
this._vmDbs = this._databasesForMigration;
break;
}
this._azureAccount = this.savedInfo.azureAccount || undefined!;
this._azureTenant = this.savedInfo.azureTenant || undefined!;
this._targetSubscription = this.savedInfo.subscription || undefined!;
this._location = this.savedInfo.location || undefined!;
this._resourceGroup = this.savedInfo.resourceGroup || undefined!;
this._targetServerInstance = this.savedInfo.targetServerInstance || undefined!;
this._databaseBackup.migrationMode = this.savedInfo.migrationMode || undefined!;
this.refreshDatabaseBackupPage = true;
this._sourceDatabaseNames = this._databasesForMigration;
this._targetDatabaseNames = this.savedInfo.targetDatabaseNames;
this._databaseBackup.networkContainerType = this.savedInfo.networkContainerType || undefined!;
this._databaseBackup.networkShares = this.savedInfo.networkShares;
this._databaseBackup.blobs = this.savedInfo.blobs;
this._databaseBackup.subscription = this.savedInfo.subscription || undefined!;
this._sqlMigrationService = this.savedInfo.sqlMigrationService;
const savedAssessmentResults = this.savedInfo.serverAssessment;
if (savedAssessmentResults) {
this._assessmentResults = savedAssessmentResults;
this._assessedDatabaseList = this.savedInfo.databaseAssessment;
}
const savedSkuRecommendation = this.savedInfo.skuRecommendation;
if (savedSkuRecommendation) {
this._skuRecommendationPerformanceDataSource = savedSkuRecommendation.skuRecommendationPerformanceDataSource;
this._skuRecommendationPerformanceLocation = savedSkuRecommendation.skuRecommendationPerformanceLocation;
this._perfDataCollectionStartDate = savedSkuRecommendation.perfDataCollectionStartDate;
this._perfDataCollectionStopDate = savedSkuRecommendation.perfDataCollectionStopDate;
this._skuTargetPercentile = savedSkuRecommendation.skuTargetPercentile;
this._skuScalingFactor = savedSkuRecommendation.skuScalingFactor;
this._skuEnablePreview = savedSkuRecommendation.skuEnablePreview;
}
return true;
} catch {
return false;
}
}
}
export interface ServerAssessment {

View File

@@ -19,6 +19,7 @@ export enum TelemetryViews {
MigrationCutoverDialog = 'MigrationCutoverDialog',
MigrationStatusDialog = 'MigrationStatusDialog',
MigrationWizardAccountSelectionPage = 'MigrationWizardAccountSelectionPage',
MigrationWizardTaSkuRecommendationPage = 'MigrationWizardTaSkuRecommendationPage',
MigrationWizardTargetSelectionPage = 'MigrationWizardTargetSelectionPage',
MigrationWizardIntegrationRuntimePage = 'MigrationWizardIntegrationRuntimePage',
MigrationWizardSummaryPage = 'MigrationWizardSummaryPage',

View File

@@ -6,15 +6,14 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { EOL } from 'os';
import { getStorageAccountAccessKeys, SqlManagedInstance, SqlVMServer, Subscription } from '../api/azure';
import { getStorageAccountAccessKeys } from '../api/azure';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { Blob, MigrationMode, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, NetworkShare, Page, StateChangeEvent } from '../models/stateMachine';
import { Blob, MigrationMode, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, NetworkShare, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import { IconPathHelper } from '../constants/iconPathHelper';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
import { findDropDownItemIndex, selectDropDownIndex } from '../api/utils';
import { findDropDownItemIndex, selectDropDownIndex, selectDefaultDropdownValue } from '../api/utils';
import { logError, TelemetryViews } from '../telemtery';
import { azureResource } from 'azureResource';
import * as styles from '../constants/styles';
const WIZARD_TABLE_COLUMN_WIDTH = '200px';
@@ -128,6 +127,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
.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'
@@ -144,6 +144,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
.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'
@@ -298,9 +299,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
return true;
}).component();
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
this._windowsUserAccountText.value = this.migrationStateModel.savedInfo.networkShares[0].windowsUser;
}
this._disposables.push(this._windowsUserAccountText.onTextChanged((value) => {
for (let i = 0; i < this.migrationStateModel._databaseBackup.networkShares.length; i++) {
this.migrationStateModel._databaseBackup.networkShares[i].windowsUser = value;
@@ -545,7 +543,11 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._networkTableContainer = this._view.modelBuilder.flexContainer().withItems([
networkShareTableText,
this._networkShareTargetDatabaseNamesTable
]).component();
]).withProps({
CSSStyles: {
'display': 'none',
}
}).component();
const allFieldsRequiredLabel = this._view.modelBuilder.text()
.withProps({
@@ -559,7 +561,11 @@ export class DatabaseBackupPage extends MigrationWizardPage {
blobTableText,
allFieldsRequiredLabel,
this._blobContainerTargetDatabaseNamesTable
]).component();
]).withProps({
CSSStyles: {
'display': 'none',
}
}).component();
const container = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
@@ -567,7 +573,9 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._networkTableContainer,
this._blobTableContainer
]).withProps({
display: 'none'
CSSStyles: {
'display': 'none',
}
}).component();
return container;
}
@@ -736,18 +744,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) {
this.migrationStateModel._databaseBackup.networkContainerType = <NetworkContainerType>this.migrationStateModel.savedInfo.networkContainerType;
this.migrationStateModel._databaseBackup.networkShares = this.migrationStateModel.savedInfo.networkShares;
this.migrationStateModel._databaseBackup.subscription = <Subscription>this.migrationStateModel.savedInfo.targetSubscription;
this.migrationStateModel._databaseBackup.blobs = this.migrationStateModel.savedInfo.blobs;
this.migrationStateModel._targetDatabaseNames = this.migrationStateModel.savedInfo.targetDatabaseNames;
}
if (this.migrationStateModel.refreshDatabaseBackupPage) {
try {
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
this.migrationStateModel._migrationDbs = this.migrationStateModel.savedInfo.databaseList;
}
const isOfflineMigration = this.migrationStateModel._databaseBackup?.migrationMode === MigrationMode.OFFLINE;
const lastBackupFileColumnIndex = this._blobContainerTargetDatabaseNamesTable.columns.length - 1;
this._blobContainerTargetDatabaseNamesTable.columns[lastBackupFileColumnIndex].hidden = !isOfflineMigration;
@@ -755,30 +753,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
column.width = isOfflineMigration ? WIZARD_TABLE_COLUMN_WIDTH_SMALL : WIZARD_TABLE_COLUMN_WIDTH;
});
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
if (this.migrationStateModel.savedInfo.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
this._networkShareButton.checked = true;
} else {
this._networkShareButton.checked = false;
this._networkTableContainer.display = 'none';
await this._networkShareContainer.updateCssStyles({ 'display': 'none' });
}
}
await this.switchNetworkContainerFields(this.migrationStateModel._databaseBackup.networkContainerType);
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
if (this.migrationStateModel.savedInfo.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
this._blobContainerButton.checked = true;
} else {
this._blobContainerButton.checked = false;
this._blobTableContainer.display = 'none';
await this._blobContainer.updateCssStyles({ 'display': 'none' });
}
}
if (!(this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
await this._targetDatabaseContainer.updateCssStyles({ 'display': 'none' });
await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'none' });
}
const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile();
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider);
const query = 'select SUSER_NAME()';
@@ -788,6 +764,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
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[0]?.windowsUser;
this._networkShareTargetDatabaseNames = [];
this._networkShareLocations = [];
@@ -800,16 +777,45 @@ export class DatabaseBackupPage extends MigrationWizardPage {
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI) {
this._existingDatabases = await this.migrationStateModel.getManagedDatabases();
}
let originalTargetDatabaseNames = this.migrationStateModel._targetDatabaseNames;
let originalNetworkShares = this.migrationStateModel._databaseBackup.networkShares;
let originalBlobs = this.migrationStateModel._databaseBackup.blobs;
if (this.migrationStateModel._didUpdateDatabasesForMigration) {
this.migrationStateModel._targetDatabaseNames = [];
this.migrationStateModel._databaseBackup.blobs = [];
this.migrationStateModel._databaseBackup.networkShares = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
this.migrationStateModel._targetDatabaseNames.push(db);
this.migrationStateModel._databaseBackup.blobs.push(<Blob>{});
this.migrationStateModel._databaseBackup.networkShares.push(<NetworkShare>{});
this.migrationStateModel._databaseBackup.blobs = [];
}
this.migrationStateModel._databasesForMigration.forEach((db, index) => {
let targetDatabaseName = db;
let networkShare = <NetworkShare>{};
let blob = <Blob>{};
if (this.migrationStateModel._didUpdateDatabasesForMigration) {
const dbIndex = this.migrationStateModel._sourceDatabaseNames?.indexOf(db);
if (dbIndex > -1) {
targetDatabaseName = originalTargetDatabaseNames[dbIndex] ?? targetDatabaseName;
networkShare = originalNetworkShares[dbIndex] ?? networkShare;
blob = originalBlobs[dbIndex] ?? blob;
} else {
// network share values are uniform for all dbs in the same migration, except for networkShareLocation
const previouslySelectedNetworkShare = originalNetworkShares[0];
if (previouslySelectedNetworkShare) {
networkShare = {
...previouslySelectedNetworkShare,
networkShareLocation: '',
};
}
}
}
this.migrationStateModel._targetDatabaseNames[index] = targetDatabaseName;
this.migrationStateModel._databaseBackup.networkShares[index] = networkShare;
this.migrationStateModel._databaseBackup.blobs[index] = blob;
const targetDatabaseInput = this._view.modelBuilder.inputBox().withProps({
required: true,
value: db,
value: targetDatabaseName,
width: WIZARD_TABLE_COLUMN_WIDTH
}).withValidation(c => {
if (this._networkShareTargetDatabaseNames.filter(t => t.value === c.value).length > 1) { //Making sure no databases have duplicate values.
@@ -830,11 +836,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
await this.validateFields();
}));
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
targetDatabaseInput.value = this.migrationStateModel.savedInfo.targetDatabaseNames[index];
} else {
targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index];
}
this._networkShareTargetDatabaseNames.push(targetDatabaseInput);
const networkShareLocationInput = this._view.modelBuilder.inputBox().withProps({
@@ -856,16 +858,12 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this.migrationStateModel._databaseBackup.networkShares[index].networkShareLocation = value.trim();
await this.validateFields();
}));
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
networkShareLocationInput.value = this.migrationStateModel.savedInfo.networkShares[index].networkShareLocation;
} else {
networkShareLocationInput.value = this.migrationStateModel._databaseBackup.networkShares[index]?.networkShareLocation;
}
this._networkShareLocations.push(networkShareLocationInput);
const blobTargetDatabaseInput = this._view.modelBuilder.inputBox().withProps({
required: true,
value: db,
value: targetDatabaseName,
}).withValidation(c => {
if (this._blobContainerTargetDatabaseNames.filter(t => t.value === c.value).length > 1) { //Making sure no databases have duplicate values.
c.validationErrorMessage = constants.DUPLICATE_NAME_ERROR;
@@ -884,11 +882,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._disposables.push(blobTargetDatabaseInput.onTextChanged((value) => {
this.migrationStateModel._targetDatabaseNames[index] = value.trim();
}));
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
blobTargetDatabaseInput.value = this.migrationStateModel.savedInfo.targetDatabaseNames[index];
} else {
targetDatabaseInput.value = this.migrationStateModel._targetDatabaseNames[index];
}
this._blobContainerTargetDatabaseNames.push(blobTargetDatabaseInput);
const blobContainerResourceDropdown = this._view.modelBuilder.dropDown().withProps({
@@ -970,10 +964,11 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._blobContainerLastBackupFileDropdowns.push(blobContainerLastBackupFileDropdown);
}
});
this.migrationStateModel._sourceDatabaseNames = this.migrationStateModel._databasesForMigration;
let data: azdata.DeclarativeTableCellValue[][] = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
this.migrationStateModel._databasesForMigration.forEach((db, index) => {
const targetRow: azdata.DeclarativeTableCellValue[] = [];
targetRow.push({
value: db
@@ -989,7 +984,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
await this._networkShareTargetDatabaseNamesTable.setDataValues(data);
data = [];
this.migrationStateModel._migrationDbs.forEach((db, index) => {
this.migrationStateModel._databasesForMigration.forEach((db, index) => {
const targetRow: azdata.DeclarativeTableCellValue[] = [];
targetRow.push({
value: db
@@ -1037,7 +1032,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
const errors: string[] = [];
switch (this.migrationStateModel._databaseBackup.networkContainerType) {
case NetworkContainerType.NETWORK_SHARE:
case NetworkContainerType.NETWORK_SHARE: {
if ((<azdata.CategoryValue>this._networkShareStorageAccountResourceGroupDropdown.value)?.displayName === constants.RESOURCE_GROUP_NOT_FOUND) {
errors.push(constants.INVALID_RESOURCE_GROUP_ERROR);
}
@@ -1045,27 +1040,29 @@ export class DatabaseBackupPage extends MigrationWizardPage {
errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
}
break;
case NetworkContainerType.BLOB_CONTAINER:
}
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._migrationDbs[index]));
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._migrationDbs[index]));
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._migrationDbs[index]));
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._migrationDbs[index]));
errors.push(constants.INVALID_BLOB_LAST_BACKUP_FILE_ERROR(this.migrationStateModel._databasesForMigration[index]));
}
});
}
@@ -1082,15 +1079,18 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
duplicates.forEach((d) => {
if (d.length > 1) {
const dupString = `${d.map(index => this.migrationStateModel._migrationDbs[index]).join(', ')}`;
const dupString = `${d.map(index => this.migrationStateModel._databasesForMigration[index]).join(', ')}`;
errors.push(constants.PROVIDE_UNIQUE_CONTAINERS + dupString);
}
});
}
break;
}
default:
return false;
}
this.migrationStateModel._targetDatabaseNames.forEach(t => {
if (this.migrationStateModel._targetType === MigrationTargetType.SQLMI && this._existingDatabases.includes(t)) { // Making sure if database with same name is not present on the target Azure SQL
errors.push(constants.DATABASE_ALREADY_EXISTS_MI(t, this.migrationStateModel._targetServerInstance.name));
@@ -1123,7 +1123,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
break;
case NetworkContainerType.NETWORK_SHARE:
// All network share migrations use the same storage account
const storageAccount = this.migrationStateModel._databaseBackup.networkShares[0].storageAccount;
const storageAccount = this.migrationStateModel._databaseBackup.networkShares[0]?.storageAccount;
const storageKey = (await getStorageAccountAccessKeys(
this.migrationStateModel._azureAccount,
this.migrationStateModel._databaseBackup.subscription,
@@ -1152,16 +1152,29 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this.wizard.nextButton.enabled = true;
this.migrationStateModel._databaseBackup.networkContainerType = containerType;
await this._blobContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none' });
await this._networkShareContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none' });
await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none' });
await this._targetDatabaseContainer.updateCssStyles({ 'display': 'inline' });
this._networkTableContainer.display = (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none';
this._blobTableContainer.display = (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none';
//Preserving the database Names between the 2 tables.
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
this.migrationStateModel._targetDatabaseNames = this.migrationStateModel.savedInfo.targetDatabaseNames;
switch (containerType) {
case NetworkContainerType.NETWORK_SHARE: {
await this._networkShareContainer.updateCssStyles({ 'display': 'inline' });
await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'inline' });
await this._networkTableContainer.updateCssStyles({ 'display': 'inline' });
await this._blobContainer.updateCssStyles({ 'display': 'none' });
await this._blobTableContainer.updateCssStyles({ 'display': 'none' });
break;
}
case NetworkContainerType.BLOB_CONTAINER: {
await this._networkShareContainer.updateCssStyles({ 'display': 'none' });
await this._networkShareStorageAccountDetails.updateCssStyles({ 'display': 'none' });
await this._networkTableContainer.updateCssStyles({ 'display': 'none' });
await this._blobContainer.updateCssStyles({ 'display': 'inline' });
await this._blobTableContainer.updateCssStyles({ 'display': 'inline' });
break;
}
}
await this._windowsUserAccountText.updateProperties({
@@ -1203,11 +1216,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
private async getSubscriptionValues(): Promise<void> {
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup)) {
this.migrationStateModel._targetSubscription = <azureResource.AzureResourceSubscription>this.migrationStateModel.savedInfo.targetSubscription;
this.migrationStateModel._targetServerInstance = <SqlManagedInstance | SqlVMServer>this.migrationStateModel.savedInfo.targetServerInstance;
}
this._networkShareContainerSubscription.value = this.migrationStateModel._targetSubscription.name;
this._networkShareContainerLocation.value = await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.location);
@@ -1224,15 +1232,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._networkShareStorageAccountResourceGroupDropdown.loading = true;
try {
this._networkShareStorageAccountResourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription);
if (this.hasSavedInfo(NetworkContainerType.NETWORK_SHARE, this._networkShareStorageAccountResourceGroupDropdown.values)) {
this._networkShareStorageAccountResourceGroupDropdown.values.forEach((resource, index) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.networkShares[0].resourceGroup?.id?.toLowerCase()) {
selectDropDownIndex(this._networkShareStorageAccountResourceGroupDropdown, index);
}
});
} else {
selectDropDownIndex(this._networkShareStorageAccountResourceGroupDropdown, 0);
}
selectDefaultDropdownValue(this._networkShareStorageAccountResourceGroupDropdown, this.migrationStateModel._databaseBackup?.networkShares[0]?.resourceGroup?.id, false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingNetworkStorageResourceGroup', error);
} finally {
@@ -1243,13 +1243,15 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async loadNetworkShareStorageDropdown(): Promise<void> {
this._networkShareContainerStorageAccountDropdown.loading = true;
this._networkShareStorageAccountResourceGroupDropdown.loading = true;
try {
this._networkShareContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.networkShares[0].resourceGroup);
selectDropDownIndex(this._networkShareContainerStorageAccountDropdown, 0);
this._networkShareContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.networkShares[0]?.resourceGroup);
selectDefaultDropdownValue(this._networkShareContainerStorageAccountDropdown, this.migrationStateModel?._databaseBackup?.networkShares[0]?.storageAccount?.id, false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingNetworkShareStorageDropdown', error);
} finally {
this._networkShareContainerStorageAccountDropdown.loading = false;
this._networkShareStorageAccountResourceGroupDropdown.loading = false;
}
}
@@ -1259,15 +1261,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
const resourceGroupValues = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._databaseBackup.subscription);
this._blobContainerResourceGroupDropdowns.forEach((dropDown, index) => {
dropDown.values = resourceGroupValues;
if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, dropDown.values)) {
dropDown.values.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.resourceGroup?.id?.toLowerCase()) {
selectDropDownIndex(dropDown, resourceIndex);
}
});
} else {
selectDropDownIndex(dropDown, 0);
}
selectDefaultDropdownValue(dropDown, this.migrationStateModel._databaseBackup?.blobs[index]?.resourceGroup?.id, false);
});
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobResourceGroup', error);
@@ -1280,15 +1274,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._blobContainerStorageAccountDropdowns[index].loading = true;
try {
this._blobContainerStorageAccountDropdowns[index].values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index]?.resourceGroup);
if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerStorageAccountDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.storageAccount)) {
this._blobContainerStorageAccountDropdowns[index].values!.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.storageAccount?.id?.toLowerCase()) {
selectDropDownIndex(this._blobContainerStorageAccountDropdowns[index], resourceIndex);
}
});
} else {
selectDropDownIndex(this._blobContainerStorageAccountDropdowns[index], 0);
}
selectDefaultDropdownValue(this._blobContainerStorageAccountDropdowns[index], this.migrationStateModel._databaseBackup?.blobs[index]?.storageAccount?.id, false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobStorageDropdown', error);
} finally {
@@ -1301,15 +1287,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
try {
const blobContainerValues = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index]?.storageAccount);
this._blobContainerDropdowns[index].values = blobContainerValues;
if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.blobContainer)) {
this._blobContainerDropdowns[index].values!.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.blobContainer?.id?.toLowerCase()) {
selectDropDownIndex(this._blobContainerDropdowns[index], resourceIndex);
}
});
} else {
selectDropDownIndex(this._blobContainerDropdowns[index], 0);
}
selectDefaultDropdownValue(this._blobContainerDropdowns[index], this.migrationStateModel._databaseBackup?.blobs[index]?.blobContainer?.id, false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobContainers', error);
} finally {
@@ -1322,15 +1300,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
try {
const blobLastBackupFileValues = await this.migrationStateModel.getBlobLastBackupFileNameValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.blobs[index]?.storageAccount, this.migrationStateModel._databaseBackup.blobs[index]?.blobContainer);
this._blobContainerLastBackupFileDropdowns[index].values = blobLastBackupFileValues;
if (this.hasSavedInfo(NetworkContainerType.BLOB_CONTAINER, this._blobContainerLastBackupFileDropdowns[index].values && this.migrationStateModel.savedInfo.blobs[index]?.lastBackupFile)) {
this._blobContainerLastBackupFileDropdowns[index].values!.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.blobs[index]?.lastBackupFile!.toLowerCase()) {
selectDropDownIndex(this._blobContainerLastBackupFileDropdowns[index], resourceIndex);
}
});
} else {
selectDropDownIndex(this._blobContainerLastBackupFileDropdowns[index], 0);
}
selectDefaultDropdownValue(this._blobContainerLastBackupFileDropdowns[index], this.migrationStateModel._databaseBackup?.blobs[index]?.lastBackupFile, false);
} catch (error) {
logError(TelemetryViews.DatabaseBackupPage, 'ErrorLoadingBlobLastBackupFiles', error);
} finally {
@@ -1362,12 +1332,4 @@ export class DatabaseBackupPage extends MigrationWizardPage {
selectDropDownIndex(this._blobContainerStorageAccountDropdowns[rowIndex], 0);
await this._blobContainerStorageAccountDropdowns[rowIndex].updateProperties(dropdownProps);
}
private hasSavedInfo(networkContainerType: NetworkContainerType, values: any): boolean {
if (this.migrationStateModel._databaseBackup.networkContainerType === networkContainerType &&
(this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseBackup) && values)) {
return true;
}
return false;
}
}

View File

@@ -6,11 +6,12 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, Page, StateChangeEvent } from '../models/stateMachine';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import { IconPath, IconPathHelper } from '../constants/iconPathHelper';
import { debounce } from '../api/utils';
import * as styles from '../constants/styles';
import { selectDatabasesFromList } from '../constants/helper';
const styleLeft: azdata.CssStyles = {
'border': 'none',
@@ -89,12 +90,14 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
}
public async onPageLeave(): Promise<void> {
const assessedDatabases = this.migrationStateModel._databaseAssessment ?? [];
const selectedDatabases = this.selectedDbs();
const assessedDatabases = this.migrationStateModel._assessedDatabaseList ?? [];
const selectedDatabases = this.migrationStateModel._databasesForAssessment;
// run assessment if
// * no prior assessment
// * the prior assessment had an error or
// * the assessed databases list is different from the selected databases list
this.migrationStateModel._runAssessments = !!this.migrationStateModel._assessmentResults?.assessmentError
this.migrationStateModel._runAssessments = !this.migrationStateModel._assessmentResults
|| !!this.migrationStateModel._assessmentResults?.assessmentError
|| assessedDatabases.length === 0
|| assessedDatabases.length !== selectedDatabases.length
|| assessedDatabases.some(db => selectedDatabases.indexOf(db) < 0);
@@ -268,20 +271,8 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
}
).component();
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.DatabaseSelector) {
await this._databaseSelectorTable.setDataValues(this.migrationStateModel.savedInfo.selectedDatabases);
} else {
if (this.migrationStateModel.retryMigration) {
const sourceDatabaseName = this.migrationStateModel.savedInfo.databaseList[0];
this._databaseTableValues.forEach((row, index) => {
const dbName = row[1].value as string;
if (dbName?.toLowerCase() === sourceDatabaseName?.toLowerCase()) {
row[0].value = true;
}
});
}
this._databaseTableValues = selectDatabasesFromList(this.migrationStateModel._databasesForAssessment, this._databaseTableValues);
await this._databaseSelectorTable.setDataValues(this._databaseTableValues);
}
await this.updateValuesOnSelection();
this._disposables.push(this._databaseSelectorTable.onDataChanged(async () => {
@@ -300,7 +291,6 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
flex.addItem(this._dbCount, { flex: '0 0 auto' });
flex.addItem(this._databaseSelectorTable);
return flex;
// insert names of databases into table
}
public selectedDbs(): string[] {
@@ -317,8 +307,7 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
await this._dbCount.updateProperties({
'value': constants.DATABASES_SELECTED(this.selectedDbs().length, this._databaseTableValues.length)
});
this.migrationStateModel._databaseAssessment = this.selectedDbs();
this.migrationStateModel.databaseSelectorTableValues = <azdata.DeclarativeTableCellValue[][]>this._databaseSelectorTable.dataValues;
this.migrationStateModel._databasesForAssessment = this.selectedDbs();
}
// undo when bug #16445 is fixed

View File

@@ -5,16 +5,15 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { azureResource } from 'azureResource';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, NetworkContainerType, Page, StateChangeEvent } from '../models/stateMachine';
import { 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';
import { getFullResourceGroupFromId, getLocationDisplayName, getSqlMigrationService, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData, SqlManagedInstance, SqlVMServer } from '../api/azure';
import { getFullResourceGroupFromId, getLocationDisplayName, getSqlMigrationService, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData } from '../api/azure';
import { IconPathHelper } from '../constants/iconPathHelper';
import { logError, TelemetryViews } from '../telemtery';
import { findDropDownItemIndex, selectDropDownIndex } from '../api/utils';
import { findDropDownItemIndex, selectDefaultDropdownValue } from '../api/utils';
import * as styles from '../constants/styles';
export class IntergrationRuntimePage extends MigrationWizardPage {
@@ -76,11 +75,6 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime)) {
this.migrationStateModel._targetSubscription = <azureResource.AzureResourceSubscription>this.migrationStateModel.savedInfo.targetSubscription;
this.migrationStateModel._targetServerInstance = <SqlManagedInstance | SqlVMServer>this.migrationStateModel.savedInfo.targetServerInstance;
}
this._subscription.value = this.migrationStateModel._targetSubscription.name;
this._location.value = await getLocationDisplayName(this.migrationStateModel._targetServerInstance.location);
this._dmsInfoContainer.display = (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE && this.migrationStateModel._sqlMigrationService) ? 'inline' : 'none';
@@ -183,10 +177,13 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}).component();
this._disposables.push(this._resourceGroupDropdown.onValueChanged(async (value) => {
const selectedIndex = findDropDownItemIndex(this._resourceGroupDropdown, value);
if (selectedIndex > -1 &&
value !== constants.RESOURCE_GROUP_NOT_FOUND) {
this.migrationStateModel._sqlMigrationServiceResourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex).name;
if (selectedIndex > -1) {
await this.populateDms(value);
} else {
this.migrationStateModel._sqlMigrationServiceResourceGroup = undefined!;
}
await this.populateDms();
}));
const migrationServiceDropdownLabel = this._view.modelBuilder.text().withProps({
@@ -216,8 +213,10 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
text: ''
};
const selectedIndex = findDropDownItemIndex(this._dmsDropdown, value);
if (selectedIndex > -1) {
this.migrationStateModel._sqlMigrationService = this.migrationStateModel.getMigrationService(selectedIndex);
await this.loadMigrationServiceStatus();
}
} else {
this.migrationStateModel._sqlMigrationService = undefined;
this._dmsInfoContainer.display = 'none';
@@ -238,7 +237,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this.migrationStateModel._sqlMigrationServiceResourceGroup = createdDmsResult.resourceGroup;
this.migrationStateModel._sqlMigrationService = createdDmsResult.service;
await this.loadResourceGroupDropdown();
await this.populateDms(createdDmsResult.resourceGroup);
await this.populateDms();
}));
const flexContainer = this._view.modelBuilder.flexContainer().withItems([
@@ -376,38 +375,26 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
return container;
}
public async loadResourceGroupDropdown(): Promise<void> {
this._resourceGroupDropdown.loading = true;
this._dmsDropdown.loading = true;
try {
this._resourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._targetSubscription);
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime && this._resourceGroupDropdown.values)) {
this._resourceGroupDropdown.values.forEach((resource, resourceIndex) => {
const resourceId = this.migrationStateModel.savedInfo?.migrationServiceId?.toLowerCase();
if (resourceId && (<azdata.CategoryValue>resource).name.toLowerCase() === getFullResourceGroupFromId(resourceId)) {
selectDropDownIndex(this._resourceGroupDropdown, resourceIndex);
}
});
}
const resourceGroup = (this.migrationStateModel._sqlMigrationService)
? getFullResourceGroupFromId(this.migrationStateModel._sqlMigrationService?.id)
: undefined;
selectDefaultDropdownValue(this._resourceGroupDropdown, resourceGroup, false);
} finally {
this._resourceGroupDropdown.loading = false;
this._dmsDropdown.loading = false;
}
}
public async populateDms(resourceGroupName: string): Promise<void> {
public async populateDms(): Promise<void> {
this._dmsDropdown.loading = true;
try {
this._dmsDropdown.values = await this.migrationStateModel.getSqlMigrationServiceValues(this.migrationStateModel._targetSubscription, <SqlManagedInstance>this.migrationStateModel._targetServerInstance, resourceGroupName);
const selectedSqlMigrationService = this._dmsDropdown.values.find(v => v.displayName.toLowerCase() === this.migrationStateModel._sqlMigrationService?.name?.toLowerCase());
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.IntegrationRuntime && this._dmsDropdown.values)) {
this._dmsDropdown.values.forEach((resource, resourceIndex) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.migrationServiceId?.toLowerCase()) {
selectDropDownIndex(this._dmsDropdown, resourceIndex);
}
});
} else {
this._dmsDropdown.value = (selectedSqlMigrationService) ? selectedSqlMigrationService : this._dmsDropdown.values[0];
}
this._dmsDropdown.values = await this.migrationStateModel.getSqlMigrationServiceValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._sqlMigrationServiceResourceGroup);
selectDefaultDropdownValue(this._dmsDropdown, this.migrationStateModel._sqlMigrationService?.id, false);
} finally {
this._dmsDropdown.loading = false;
}

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationMode, MigrationStateModel, Page, StateChangeEvent } from '../models/stateMachine';
import { MigrationMode, MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import * as styles from '../constants/styles';
@@ -17,6 +17,7 @@ export class MigrationModePage extends MigrationWizardPage {
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> {
@@ -59,7 +60,7 @@ export class MigrationModePage extends MigrationWizardPage {
});
}
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (this.originalMigrationMode !== this.migrationStateModel._databaseBackup.migrationMode || this.migrationStateModel.resumeAssessment) {
if (this.originalMigrationMode !== this.migrationStateModel._databaseBackup.migrationMode) {
this.migrationStateModel.refreshDatabaseBackupPage = true;
}
@@ -71,15 +72,15 @@ export class MigrationModePage extends MigrationWizardPage {
}
private migrationModeContainer(): azdata.FormComponent {
const buttonGroup = 'cutoverContainer';
const buttonGroup = 'migrationMode';
const 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,
},
checked: true
}).component();
const onlineDescription = this._view.modelBuilder.text().withProps({
@@ -99,6 +100,7 @@ export class MigrationModePage extends MigrationWizardPage {
const 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'
@@ -113,16 +115,6 @@ export class MigrationModePage extends MigrationWizardPage {
}
}).component();
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.MigrationMode)) {
if (this.migrationStateModel.savedInfo.migrationMode === MigrationMode.ONLINE) {
onlineButton.checked = true;
offlineButton.checked = false;
} else {
onlineButton.checked = false;
offlineButton.checked = true;
}
}
this._disposables.push(offlineButton.onDidChangeCheckedState((e) => {
if (e) {
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE;

View File

@@ -7,7 +7,7 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as mssql from '../../../mssql';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, MigrationTargetType, Page, PerformanceDataSourceOptions, ServerAssessment, StateChangeEvent } from '../models/stateMachine';
import { MigrationStateModel, MigrationTargetType, PerformanceDataSourceOptions, StateChangeEvent } from '../models/stateMachine';
import { AssessmentResultsDialog } from '../dialog/assessmentResults/assessmentResultsDialog';
import { SkuRecommendationResultsDialog } from '../dialog/skuRecommendationResults/skuRecommendationResultsDialog';
import { GetAzureRecommendationDialog } from '../dialog/skuRecommendationResults/getAzureRecommendationDialog';
@@ -17,6 +17,7 @@ import { IconPath, IconPathHelper } from '../constants/iconPathHelper';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
import * as styles from '../constants/styles';
import { SkuEditParametersDialog } from '../dialog/skuRecommendationResults/skuEditParametersDialog';
import { logError, TelemetryViews } from '../telemtery';
export interface Product {
type: MigrationTargetType;
@@ -153,7 +154,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._disposables.push(refreshAssessmentButton.onDidClick(async () => {
await this.startCardLoading();
await this.migrationStateModel.getSkuRecommendations();
this.migrationStateModel._runAssessments = true;
await this.constructDetails();
}));
@@ -382,12 +383,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
}
}).component();
let serverName = '';
if (this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.serverName)) {
serverName = this.migrationStateModel.serverName;
} else {
serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
}
let serverName = this.migrationStateModel.serverName || (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
let miDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLMI);
let vmDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLVM);
@@ -419,37 +415,31 @@ export class SKURecommendationPage extends MigrationWizardPage {
}
private async changeTargetType(newTargetType: string) {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) {
this.migrationStateModel._databaseAssessment = <string[]>this.migrationStateModel.savedInfo.databaseAssessment;
}
// remove assessed databases that have been removed from the source selection list
switch (newTargetType) {
case MigrationTargetType.SQLMI: {
const miDbs = this.migrationStateModel._miDbs.filter(
db => this.migrationStateModel._databaseAssessment.findIndex(
db => this.migrationStateModel._databasesForAssessment.findIndex(
dba => dba === db) >= 0);
const vmDbs = this.migrationStateModel._vmDbs.filter(
db => this.migrationStateModel._databaseAssessment.findIndex(
dba => dba === db) >= 0);
if (newTargetType === MigrationTargetType.SQLMI) {
this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI;
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation) {
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel.savedInfo.databaseList.length, this.migrationStateModel._databaseAssessment.length);
} else {
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(miDbs.length, this.migrationStateModel._databaseAssessment.length);
}
this.migrationStateModel._targetType = MigrationTargetType.SQLMI;
this.migrationStateModel._migrationDbs = miDbs;
} else {
this.migrationStateModel._databasesForMigration = miDbs;
break;
}
case MigrationTargetType.SQLVM: {
const vmDbs = this.migrationStateModel._vmDbs.filter(
db => this.migrationStateModel._databasesForAssessment.findIndex(
dba => dba === db) >= 0);
this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_VM;
if ((this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation)) {
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel.savedInfo.databaseList.length, this.migrationStateModel._databaseAssessment.length);
} else {
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(vmDbs.length, this.migrationStateModel._databaseAssessment.length);
}
this.migrationStateModel._targetType = MigrationTargetType.SQLVM;
this.migrationStateModel._migrationDbs = vmDbs;
this.migrationStateModel._databasesForMigration = vmDbs;
break;
}
}
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel._databasesForMigration.length, this.migrationStateModel._databasesForAssessment.length);
this.migrationStateModel.refreshDatabaseBackupPage = true;
}
@@ -459,17 +449,13 @@ export class SKURecommendationPage extends MigrationWizardPage {
level: azdata.window.MessageLevel.Error
};
await this._setAssessmentState(true, false);
const serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
const errors: string[] = [];
try {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage) {
this.migrationStateModel._assessmentResults = <ServerAssessment>this.migrationStateModel.savedInfo.serverAssessment;
} else {
await this.migrationStateModel.getDatabaseAssessments(MigrationTargetType.SQLMI);
}
if (this.migrationStateModel._runAssessments) {
const errors: string[] = [];
await this._setAssessmentState(true, false);
try {
await this.migrationStateModel.getDatabaseAssessments(MigrationTargetType.SQLMI);
const assessmentError = this.migrationStateModel._assessmentResults?.assessmentError;
if (assessmentError) {
errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`);
@@ -479,8 +465,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
e => `message: ${e.message}${EOL}errorSummary: ${e.errorSummary}${EOL}possibleCauses: ${e.possibleCauses}${EOL}guidance: ${e.guidance}${EOL}errorId: ${e.errorId}`)!);
}
} catch (e) {
console.log(e);
errors.push(constants.SKU_RECOMMENDATION_ASSESSMENT_UNEXPECTED_ERROR(serverName, e));
logError(TelemetryViews.MigrationWizardTaSkuRecommendationPage, 'SkuRecommendationUnexpectedError', e);
} finally {
this.migrationStateModel._runAssessments = errors.length > 0;
if (errors.length > 0) {
@@ -498,37 +484,27 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults?.databaseAssessments?.length);
}
}
if (this.hasRecommendations() && this.migrationStateModel.hasRecommendedDatabaseListChanged()) {
await this.migrationStateModel.getSkuRecommendations();
}
if (this.hasSavedInfo()) {
if (this.migrationStateModel.savedInfo.migrationTargetType) {
this._rbg.selectedCardId = this.migrationStateModel.savedInfo.migrationTargetType;
await this.refreshCardText();
}
if (this.migrationStateModel.savedInfo.migrationTargetType === MigrationTargetType.SQLMI) {
this.migrationStateModel._miDbs = this.migrationStateModel.savedInfo.databaseList;
} else {
this.migrationStateModel._vmDbs = this.migrationStateModel.savedInfo.databaseList;
// use prior assessment results
this._assessmentStatusIcon.iconPath = IconPathHelper.completedMigration;
this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName);
this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults?.databaseAssessments?.length);
}
if (this.migrationStateModel.savedInfo.skuRecommendation) {
const skuRecommendationSavedInfo = this.migrationStateModel.savedInfo.skuRecommendation;
this.migrationStateModel._skuRecommendationPerformanceDataSource = skuRecommendationSavedInfo.skuRecommendationPerformanceDataSource!;
this.migrationStateModel._skuRecommendationPerformanceLocation = skuRecommendationSavedInfo.skuRecommendationPerformanceLocation!;
if (this.migrationStateModel.savedInfo?.migrationTargetType) {
this._rbg.selectedCardId = this.migrationStateModel._targetType;
}
this.migrationStateModel._skuScalingFactor = skuRecommendationSavedInfo.skuScalingFactor!;
this.migrationStateModel._skuTargetPercentile = skuRecommendationSavedInfo.skuTargetPercentile!;
this.migrationStateModel._skuEnablePreview = skuRecommendationSavedInfo.skuEnablePreview!;
let shouldGetSkuRecommendations = false;
if (this.hasRecommendations() && this.migrationStateModel.hasRecommendedDatabaseListChanged()) {
shouldGetSkuRecommendations = true;
}
if (this.migrationStateModel.savedInfo?.skuRecommendation) {
await this.refreshSkuParameters();
switch (this.migrationStateModel._skuRecommendationPerformanceDataSource) {
case PerformanceDataSourceOptions.CollectData: {
this.migrationStateModel._perfDataCollectionStartDate = skuRecommendationSavedInfo.perfDataCollectionStartDate;
// check if collector is still running
await this.migrationStateModel.refreshPerfDataCollection();
if (this.migrationStateModel._perfDataCollectionIsCollecting) {
@@ -542,17 +518,20 @@ export class SKURecommendationPage extends MigrationWizardPage {
// user started collecting data, but collector is stopped
// set stop date to some date value
this.migrationStateModel._perfDataCollectionStopDate = this.migrationStateModel._perfDataCollectionStopDate || new Date();
await this.migrationStateModel.getSkuRecommendations();
shouldGetSkuRecommendations = true;
}
break;
}
case PerformanceDataSourceOptions.OpenExisting: {
await this.migrationStateModel.getSkuRecommendations();
shouldGetSkuRecommendations = true;
break;
}
}
}
if (shouldGetSkuRecommendations) {
await this.migrationStateModel.getSkuRecommendations();
}
await this.refreshSkuRecommendationComponents();
@@ -582,7 +561,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
display = (this._rbg.selectedCardId
&& (!failedAssessment || this._skipAssessmentCheckbox.checked)
&& this.migrationStateModel._migrationDbs.length > 0)
&& this.migrationStateModel._databasesForMigration?.length > 0)
? 'inline'
: 'none';
@@ -603,7 +582,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
if (this._rbg.selectedCardId === undefined || this._rbg.selectedCardId === '') {
errors.push(constants.SELECT_TARGET_TO_CONTINUE);
}
if (this.migrationStateModel._migrationDbs.length === 0) {
if (this.migrationStateModel._databasesForMigration.length === 0) {
errors.push(constants.SELECT_DATABASE_TO_MIGRATE);
}
@@ -638,9 +617,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
public async refreshCardText(showLoadingIcon: boolean = true): Promise<void> {
this._rbgLoader.loading = showLoadingIcon && true;
if (this._rbg.selectedCardId === MigrationTargetType.SQLMI) {
this.migrationStateModel._migrationDbs = this.migrationStateModel._miDbs;
this.migrationStateModel._databasesForMigration = this.migrationStateModel._miDbs;
} else {
this.migrationStateModel._migrationDbs = this.migrationStateModel._vmDbs;
this.migrationStateModel._databasesForMigration = this.migrationStateModel._vmDbs;
}
const dbCount = this.migrationStateModel._assessmentResults?.databaseAssessments?.length;
@@ -1188,10 +1167,6 @@ export class SKURecommendationPage extends MigrationWizardPage {
await this.refreshCardText(false);
}
private hasSavedInfo(): boolean {
return this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.SKURecommendation);
}
private hasRecommendations(): boolean {
return this.migrationStateModel._skuRecommendationResults?.recommendations && !this.migrationStateModel._skuRecommendationResults?.recommendationError ? true : false;
}

View File

@@ -6,14 +6,12 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationMode, MigrationStateModel, MigrationTargetType, NetworkContainerType, Page, StateChangeEvent } from '../models/stateMachine';
import { MigrationMode, MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import { createHeadingTextComponent, createInformationRow, createLabelTextComponent } from './wizardController';
import { getResourceGroupFromId, Subscription } from '../api/azure';
import { getResourceGroupFromId } from '../api/azure';
import { TargetDatabaseSummaryDialog } from '../dialog/targetDatabaseSummary/targetDatabaseSummaryDialog';
import * as styles from '../constants/styles';
import { azureResource } from 'azureResource';
import { Tenant } from 'azurecore';
export class SummaryPage extends MigrationWizardPage {
private _view!: azdata.ModelView;
@@ -47,30 +45,10 @@ export class SummaryPage extends MigrationWizardPage {
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
if (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.Summary) {
this.migrationStateModel._databaseBackup.networkContainerType = <NetworkContainerType>this.migrationStateModel.savedInfo.networkContainerType;
this.migrationStateModel._databaseBackup.networkShares = this.migrationStateModel.savedInfo.networkShares;
this.migrationStateModel._databaseBackup.subscription = <Subscription>this.migrationStateModel.savedInfo.targetSubscription;
this.migrationStateModel._databaseBackup.blobs = this.migrationStateModel.savedInfo.blobs;
this.migrationStateModel._targetDatabaseNames = this.migrationStateModel.savedInfo.targetDatabaseNames;
this.migrationStateModel._targetType = <MigrationTargetType>this.migrationStateModel.savedInfo.migrationTargetType;
this.migrationStateModel._databaseAssessment = <string[]>this.migrationStateModel.savedInfo.databaseAssessment;
this.migrationStateModel._migrationDbs = this.migrationStateModel.savedInfo.databaseList;
this.migrationStateModel._targetSubscription = <azureResource.AzureResourceSubscription>this.migrationStateModel.savedInfo.subscription;
this.migrationStateModel._location = <azureResource.AzureLocation>this.migrationStateModel.savedInfo.location;
this.migrationStateModel._resourceGroup = <azureResource.AzureResourceResourceGroup>this.migrationStateModel.savedInfo.resourceGroup;
this.migrationStateModel._targetServerInstance = <azureResource.AzureSqlManagedInstance>this.migrationStateModel.savedInfo.targetServerInstance;
this.migrationStateModel.databaseSelectorTableValues = this.migrationStateModel.savedInfo.selectedDatabases;
this.migrationStateModel._azureAccount = <azdata.Account>this.migrationStateModel.savedInfo.azureAccount;
this.migrationStateModel._azureTenant = <Tenant>this.migrationStateModel.savedInfo.azureTenant;
}
const targetDatabaseSummary = new TargetDatabaseSummaryDialog(this.migrationStateModel);
const targetDatabaseHyperlink = this._view.modelBuilder.hyperlink().withProps({
url: '',
label: this.migrationStateModel._migrationDbs.length.toString(),
label: this.migrationStateModel._databasesForMigration?.length.toString(),
CSSStyles: {
...styles.BODY_CSS,
'margin': '0px',
@@ -128,7 +106,7 @@ export class SummaryPage extends MigrationWizardPage {
await createHeadingTextComponent(this._view, constants.IR_PAGE_TITLE),
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name),
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._sqlMigrationService?.location!),
createInformationRow(this._view, constants.LOCATION, await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._sqlMigrationService?.location!)),
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._sqlMigrationService?.properties?.resourceGroup!),
createInformationRow(this._view, constants.IR_PAGE_TITLE, this.migrationStateModel._sqlMigrationService?.name!)
]

View File

@@ -7,11 +7,11 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { EOL } from 'os';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, MigrationTargetType, Page, StateChangeEvent } from '../models/stateMachine';
import { MigrationStateModel, MigrationTargetType, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import * as styles from '../constants/styles';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
import { deepClone, findDropDownItemIndex, selectDropDownIndex } from '../api/utils';
import { deepClone, findDropDownItemIndex, selectDropDownIndex, selectDefaultDropdownValue } from '../api/utils';
export class TargetSelectionPage extends MigrationWizardPage {
private _view!: azdata.ModelView;
@@ -27,7 +27,6 @@ export class TargetSelectionPage extends MigrationWizardPage {
private _azureResourceDropdownLabel!: azdata.TextComponent;
private _azureResourceDropdown!: azdata.DropDownComponent;
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.AZURE_SQL_TARGET_PAGE_TITLE), migrationStateModel);
}
@@ -73,19 +72,21 @@ export class TargetSelectionPage extends MigrationWizardPage {
}
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
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;
}
await this.populateResourceInstanceDropdown();
await this.populateAzureAccountsDropdown();
this.wizard.registerNavigationValidator((pageChangeInfo) => {
@@ -99,23 +100,40 @@ export class TargetSelectionPage extends MigrationWizardPage {
return true;
}
if ((<azdata.CategoryValue>this._azureSubscriptionDropdown.value)?.displayName === constants.NO_SUBSCRIPTIONS_FOUND) {
if (!this.migrationStateModel._azureAccount) {
errors.push(constants.INVALID_ACCOUNT_ERROR);
}
if (!this.migrationStateModel._targetSubscription ||
(<azdata.CategoryValue>this._azureSubscriptionDropdown.value)?.displayName === constants.NO_SUBSCRIPTIONS_FOUND) {
errors.push(constants.INVALID_SUBSCRIPTION_ERROR);
}
if ((<azdata.CategoryValue>this._azureLocationDropdown.value)?.displayName === constants.NO_LOCATION_FOUND) {
if (!this.migrationStateModel._location ||
(<azdata.CategoryValue>this._azureLocationDropdown.value)?.displayName === constants.NO_LOCATION_FOUND) {
errors.push(constants.INVALID_LOCATION_ERROR);
}
if ((<azdata.CategoryValue>this._azureResourceGroupDropdown.value)?.displayName === constants.RESOURCE_GROUP_NOT_FOUND) {
if (!this.migrationStateModel._resourceGroup ||
(<azdata.CategoryValue>this._azureResourceGroupDropdown.value)?.displayName === constants.RESOURCE_GROUP_NOT_FOUND) {
errors.push(constants.INVALID_RESOURCE_GROUP_ERROR);
}
const resourceDropdownValue = (<azdata.CategoryValue>this._azureResourceDropdown.value)?.displayName;
if (resourceDropdownValue === constants.NO_MANAGED_INSTANCE_FOUND) {
switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLMI: {
if (!this.migrationStateModel._targetServerInstance ||
resourceDropdownValue === constants.NO_MANAGED_INSTANCE_FOUND) {
errors.push(constants.INVALID_MANAGED_INSTANCE_ERROR);
}
else if (resourceDropdownValue === constants.NO_VIRTUAL_MACHINE_FOUND) {
break;
}
case MigrationTargetType.SQLVM: {
if (!this.migrationStateModel._targetServerInstance ||
resourceDropdownValue === constants.NO_VIRTUAL_MACHINE_FOUND) {
errors.push(constants.INVALID_VIRTUAL_MACHINE_ERROR);
}
break;
}
}
if (errors.length > 0) {
this.wizard.message = {
@@ -175,8 +193,10 @@ export class TargetSelectionPage extends MigrationWizardPage {
});
}
await this._azureAccountsDropdown.validate();
await this.populateSubscriptionDropdown();
} else {
this.migrationStateModel._azureAccount = undefined!;
}
await this.populateSubscriptionDropdown();
}));
const linkAccountButton = this._view.modelBuilder.hyperlink()
@@ -236,11 +256,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
this.migrationStateModel._azureTenant = deepClone(selectedTenant);
if (selectedIndex > -1) {
this.migrationStateModel._azureAccount.properties.tenants = [this.migrationStateModel.getTenant(selectedIndex)];
this.migrationStateModel._subscriptions = undefined!;
this.migrationStateModel._targetSubscription = undefined!;
this.migrationStateModel._databaseBackup.subscription = undefined!;
}
}));
this._accountTenantFlexContainer = this._view.modelBuilder.flexContainer()
@@ -285,10 +301,12 @@ export class TargetSelectionPage extends MigrationWizardPage {
if (selectedIndex > -1 &&
value !== constants.NO_SUBSCRIPTIONS_FOUND) {
this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(selectedIndex);
this.migrationStateModel._targetServerInstance = undefined!;
this.migrationStateModel._sqlMigrationService = undefined!;
await this.populateLocationDropdown();
} else {
this.migrationStateModel._targetSubscription = undefined!;
}
this.migrationStateModel.refreshDatabaseBackupPage = true;
await this.populateLocationDropdown();
await this.populateResourceGroupDropdown();
}));
const azureLocationLabel = this._view.modelBuilder.text().withProps({
@@ -315,8 +333,11 @@ export class TargetSelectionPage extends MigrationWizardPage {
if (selectedIndex > -1 &&
value !== constants.NO_LOCATION_FOUND) {
this.migrationStateModel._location = this.migrationStateModel.getLocation(selectedIndex);
await this.populateResourceGroupDropdown();
} else {
this.migrationStateModel._location = undefined!;
}
this.migrationStateModel.refreshDatabaseBackupPage = true;
await this.populateResourceInstanceDropdown();
}));
const azureResourceGroupLabel = this._view.modelBuilder.text().withProps({
@@ -340,12 +361,13 @@ export class TargetSelectionPage extends MigrationWizardPage {
}).component();
this._disposables.push(this._azureResourceGroupDropdown.onValueChanged(async (value) => {
const selectedIndex = findDropDownItemIndex(this._azureResourceGroupDropdown, value);
if (selectedIndex > -1) {
if (value !== constants.RESOURCE_GROUP_NOT_FOUND) {
if (selectedIndex > -1 &&
value !== constants.RESOURCE_GROUP_NOT_FOUND) {
this.migrationStateModel._resourceGroup = this.migrationStateModel.getAzureResourceGroup(selectedIndex);
} else {
this.migrationStateModel._resourceGroup = undefined!;
}
await this.populateResourceInstanceDropdown();
}
}));
this._azureResourceDropdownLabel = this._view.modelBuilder.text().withProps({
@@ -367,7 +389,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
'margin-top': '-1em'
},
}).component();
this._disposables.push(this._azureResourceDropdown.onValueChanged(value => {
this._disposables.push(this._azureResourceDropdown.onValueChanged(async (value) => {
const selectedIndex = findDropDownItemIndex(this._azureResourceDropdown, value);
if (selectedIndex > -1 &&
value !== constants.NO_MANAGED_INSTANCE_FOUND &&
@@ -383,6 +405,8 @@ export class TargetSelectionPage extends MigrationWizardPage {
this.migrationStateModel._targetServerInstance = this.migrationStateModel.getManagedInstance(selectedIndex);
break;
}
} else {
this.migrationStateModel._targetServerInstance = undefined!;
}
}));
@@ -404,145 +428,91 @@ export class TargetSelectionPage extends MigrationWizardPage {
private async populateAzureAccountsDropdown(): Promise<void> {
try {
this._azureAccountsDropdown.loading = true;
this._azureSubscriptionDropdown.loading = true;
this._azureLocationDropdown.loading = true;
this._azureResourceGroupDropdown.loading = true;
this._azureResourceDropdown.loading = true;
this.updateDropdownLoadingStatus(TargetDropDowns.AzureAccount, true);
this._azureAccountsDropdown.values = await this.migrationStateModel.getAccountValues();
if (this.hasSavedInfo() && this._azureAccountsDropdown.values) {
(<azdata.CategoryValue[]>this._azureAccountsDropdown.values)?.forEach((account, index) => {
if ((<azdata.CategoryValue>account).name.toLowerCase() === this.migrationStateModel.savedInfo.azureAccount?.displayInfo.userId.toLowerCase()) {
selectDropDownIndex(this._azureAccountsDropdown, index);
}
});
} else {
selectDropDownIndex(this._azureAccountsDropdown, 0);
}
selectDefaultDropdownValue(this._azureAccountsDropdown, this.migrationStateModel._azureAccount?.displayInfo?.userId, false);
} finally {
this._azureAccountsDropdown.loading = false;
this._azureSubscriptionDropdown.loading = false;
this._azureLocationDropdown.loading = false;
this._azureResourceGroupDropdown.loading = false;
this._azureResourceDropdown.loading = false;
this.updateDropdownLoadingStatus(TargetDropDowns.AzureAccount, false);
}
}
private async populateSubscriptionDropdown(): Promise<void> {
try {
this._azureSubscriptionDropdown.loading = true;
this._azureLocationDropdown.loading = true;
this._azureResourceGroupDropdown.loading = true;
this._azureResourceDropdown.loading = true;
this.updateDropdownLoadingStatus(TargetDropDowns.Subscription, true);
this._azureSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues();
if (this.hasSavedInfo() && this._azureSubscriptionDropdown.values) {
this._azureSubscriptionDropdown.values!.forEach((subscription, index) => {
if ((<azdata.CategoryValue>subscription).name.toLowerCase() === this.migrationStateModel.savedInfo?.subscription?.id.toLowerCase()) {
selectDropDownIndex(this._azureSubscriptionDropdown, index);
}
});
} else {
selectDropDownIndex(this._azureSubscriptionDropdown, 0);
}
selectDefaultDropdownValue(this._azureSubscriptionDropdown, this.migrationStateModel._targetSubscription?.id, false);
} catch (e) {
console.log(e);
} finally {
this._azureSubscriptionDropdown.loading = false;
this._azureLocationDropdown.loading = false;
this._azureResourceGroupDropdown.loading = false;
this._azureResourceDropdown.loading = false;
this.updateDropdownLoadingStatus(TargetDropDowns.Subscription, false);
}
}
public async populateLocationDropdown(): Promise<void> {
try {
this._azureLocationDropdown.loading = true;
this._azureResourceGroupDropdown.loading = true;
this._azureResourceDropdown.loading = true;
this.updateDropdownLoadingStatus(TargetDropDowns.Location, true);
this._azureLocationDropdown.values = await this.migrationStateModel.getAzureLocationDropdownValues(this.migrationStateModel._targetSubscription);
if (this.hasSavedInfo() && this._azureLocationDropdown.values) {
this._azureLocationDropdown.values.forEach((location, index) => {
if ((<azdata.CategoryValue>location)?.displayName.toLowerCase() === this.migrationStateModel.savedInfo?.location?.displayName.toLowerCase()) {
selectDropDownIndex(this._azureLocationDropdown, index);
}
});
} else {
selectDropDownIndex(this._azureLocationDropdown, 0);
}
selectDefaultDropdownValue(this._azureLocationDropdown, this.migrationStateModel._location?.displayName, true);
} catch (e) {
console.log(e);
} finally {
this._azureLocationDropdown.loading = false;
this._azureResourceGroupDropdown.loading = false;
this._azureResourceDropdown.loading = false;
this.updateDropdownLoadingStatus(TargetDropDowns.Location, false);
}
}
public async populateResourceGroupDropdown(): Promise<void> {
try {
this._azureResourceGroupDropdown.loading = true;
this._azureResourceDropdown.loading = true;
this.updateDropdownLoadingStatus(TargetDropDowns.ResourceGroup, true);
this._azureResourceGroupDropdown.values = await this.migrationStateModel.getAzureResourceGroupDropdownValues(this.migrationStateModel._targetSubscription);
if (this.hasSavedInfo() && this._azureResourceGroupDropdown.values) {
this._azureResourceGroupDropdown.values.forEach((resourceGroup, index) => {
if ((<azdata.CategoryValue>resourceGroup)?.name.toLowerCase() === this.migrationStateModel.savedInfo?.resourceGroup?.id.toLowerCase()) {
selectDropDownIndex(this._azureResourceGroupDropdown, index);
}
});
} else {
selectDropDownIndex(this._azureResourceGroupDropdown, 0);
}
selectDefaultDropdownValue(this._azureResourceGroupDropdown, this.migrationStateModel._resourceGroup?.id, false);
} catch (e) {
console.log(e);
} finally {
this._azureResourceGroupDropdown.loading = false;
this._azureResourceDropdown.loading = false;
this.updateDropdownLoadingStatus(TargetDropDowns.ResourceGroup, false);
}
}
private async populateResourceInstanceDropdown(): Promise<void> {
try {
this._azureResourceDropdown.loading = true;
this.updateDropdownLoadingStatus(TargetDropDowns.ResourceInstance, true);
switch (this.migrationStateModel._targetType) {
case MigrationTargetType.SQLVM:
this._azureResourceDropdown.values = await this.migrationStateModel.getSqlVirtualMachineValues(
this.migrationStateModel._targetSubscription,
this.migrationStateModel._location,
this.migrationStateModel._resourceGroup);
break;
case MigrationTargetType.SQLMI:
this._azureResourceDropdown.values = await this.migrationStateModel.getManagedInstanceValues(
this.migrationStateModel._targetSubscription,
this.migrationStateModel._location,
this.migrationStateModel._resourceGroup);
case MigrationTargetType.SQLMI: {
this._azureResourceDropdown.values = await this.migrationStateModel.getManagedInstanceValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._location, this.migrationStateModel._resourceGroup);
break;
}
if (this.hasSavedInfo() && this._azureResourceDropdown.values) {
this._azureResourceDropdown.values.forEach((resource, index) => {
if ((<azdata.CategoryValue>resource).name.toLowerCase() === this.migrationStateModel.savedInfo?.targetServerInstance?.name.toLowerCase()) {
selectDropDownIndex(this._azureResourceDropdown, index);
case MigrationTargetType.SQLVM: {
this._azureResourceDropdown.values = await this.migrationStateModel.getSqlVirtualMachineValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._location, this.migrationStateModel._resourceGroup);
break;
}
});
} else {
selectDropDownIndex(this._azureResourceDropdown, 0);
}
selectDefaultDropdownValue(this._azureResourceDropdown, this.migrationStateModel._targetServerInstance?.name, true);
} catch (e) {
console.log(e);
} finally {
this._azureResourceDropdown.loading = false;
this.updateDropdownLoadingStatus(TargetDropDowns.ResourceInstance, false);
}
}
private hasSavedInfo(): boolean {
return this.migrationStateModel.retryMigration || (this.migrationStateModel.resumeAssessment && this.migrationStateModel.savedInfo.closedPage >= Page.TargetSelection);
private updateDropdownLoadingStatus(dropdown: TargetDropDowns, loading: boolean): void {
switch (dropdown) {
case TargetDropDowns.AzureAccount:
this._azureAccountsDropdown.loading = loading;
case TargetDropDowns.Subscription:
this._azureSubscriptionDropdown.loading = loading;
case TargetDropDowns.Location:
this._azureLocationDropdown.loading = loading;
case TargetDropDowns.ResourceGroup:
this._azureResourceGroupDropdown.loading = loading;
case TargetDropDowns.ResourceInstance:
this._azureResourceDropdown.loading = loading;
}
}
}
export enum TargetDropDowns {
AzureAccount,
Subscription,
Location,
ResourceGroup,
ResourceInstance,
}