SQL-Migration: improve SQL DB table selection ux to include missing tables (#22659)

* add missing target tables ux

* fix number formatting
This commit is contained in:
brian-harris
2023-04-07 16:00:12 -07:00
committed by GitHub
parent 0412ba194b
commit a60d6107b4
7 changed files with 213 additions and 85 deletions

View File

@@ -807,7 +807,9 @@ export function sortResourceArrayByName(resourceArray: SortableAzureResources[])
export function getMigrationTargetId(migration: DatabaseMigration): string { export function getMigrationTargetId(migration: DatabaseMigration): string {
// `${targetServerId}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=${DMSV2_API_VERSION}` // `${targetServerId}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=${DMSV2_API_VERSION}`
const paths = migration.id.split('/providers/Microsoft.DataMigration/', 1); const paths = migration.id.split('/providers/Microsoft.DataMigration/', 1);
return paths[0]; return paths?.length > 0
? paths[0]
: '';
} }
export function getMigrationTargetName(migration: DatabaseMigration): string { export function getMigrationTargetName(migration: DatabaseMigration): string {

View File

@@ -667,21 +667,24 @@ export const SELECT_RESOURCE_GROUP_PROMPT = localize('sql.migration.blob.resourc
export const SELECT_STORAGE_ACCOUNT = localize('sql.migration.blob.storageAccount.select', "Select a storage account value first."); export const SELECT_STORAGE_ACCOUNT = localize('sql.migration.blob.storageAccount.select', "Select a storage account value first.");
export const SELECT_BLOB_CONTAINER = localize('sql.migration.blob.container.select', "Select a blob container value first."); export const SELECT_BLOB_CONTAINER = localize('sql.migration.blob.container.select', "Select a blob container value first.");
export const MISSING_TABLE_NAME_COLUMN = localize('sql.migration.missing.table.name.column', "Table name");
export function SELECT_DATABASE_TABLES_TITLE(targetDatabaseName: string): string { export function SELECT_DATABASE_TABLES_TITLE(targetDatabaseName: string): string {
return localize('sql.migration.table.select.label', "Select tables for {0}", targetDatabaseName); return localize('sql.migration.table.select.label', "Select tables for {0}", targetDatabaseName);
} }
export const TABLE_SELECTION_EDIT = localize('sql.migration.table.selection.edit', "Edit"); export const TABLE_SELECTION_EDIT = localize('sql.migration.table.selection.edit', "Edit");
export function TABLE_SELECTION_COUNT(selectedCount: number, rowCount: number): string { export function TABLE_SELECTION_COUNT(selectedCount: number, rowCount: number): string {
return localize('sql.migration.table.selection.count', "{0} of {1}", selectedCount, rowCount); return localize('sql.migration.table.selection.count', "{0} of {1}", formatNumber(selectedCount), formatNumber(rowCount));
} }
export function TABLE_SELECTED_COUNT(selectedCount: number, rowCount: number): string { export function TABLE_SELECTED_COUNT(selectedCount: number, rowCount: number): string {
return localize('sql.migration.table.selected.count', "{0} of {1} tables selected", selectedCount, rowCount); return localize('sql.migration.table.selected.count', "{0} of {1} tables selected", formatNumber(selectedCount), formatNumber(rowCount));
} }
export function MISSING_TARGET_TABLES_COUNT(tables: number): string { export function MISSING_TARGET_TABLES_COUNT(tables: number): string {
return localize('sql.migration.table.missing.count', "Missing target tables excluded from list: {0}", tables); return localize('sql.migration.table.missing.count', "Tables missing on target: {0}", formatNumber(tables));
} }
export const DATABASE_MISSING_TABLES = localize('sql.migration.database.missing.tables', "0 tables found."); export const SELECT_TABLES_FOR_MIGRATION = localize('sql.migration.select.migration.tables', "Select tables for migration");
export const DATABASE_MISSING_TABLES = localize('sql.migration.database.missing.tables', "0 tables found on source database.");
export const DATABASE_LOADING_TABLES = localize('sql.migration.database.loading.tables', "Loading tables list..."); export const DATABASE_LOADING_TABLES = localize('sql.migration.database.loading.tables', "Loading tables list...");
export const TABLE_SELECTION_FILTER = localize('sql.migration.table.selection.filter', "Filter tables"); export const TABLE_SELECTION_FILTER = localize('sql.migration.table.selection.filter', "Filter tables");
export const TABLE_SELECTION_UPDATE_BUTTON = localize('sql.migration.table.selection.update.button', "Update"); export const TABLE_SELECTION_UPDATE_BUTTON = localize('sql.migration.table.selection.update.button', "Update");
@@ -932,7 +935,9 @@ export const AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS = localize('sql.migration.a
export const SHIR = localize('sql.migration.shir', "Self-hosted integration runtime node"); export const SHIR = localize('sql.migration.shir', "Self-hosted integration runtime node");
export const DATABASE_TO_BE_MIGRATED = localize('sql.migration.database.to.be.migrated', "Database to be migrated"); export const DATABASE_TO_BE_MIGRATED = localize('sql.migration.database.to.be.migrated', "Database to be migrated");
export function COUNT_DATABASES(count: number): string { export function COUNT_DATABASES(count: number): string {
return (count === 1) ? localize('sql.migration.count.database.single', "{0} database", count) : localize('sql.migration.count.database.multiple', "{0} databases", count); return (count === 1)
? localize('sql.migration.count.database.single', "{0} database", count)
: localize('sql.migration.count.database.multiple', "{0} databases", formatNumber(count));
} }
export function TOTAL_TABLES_SELECTED(selected: number, total: number): string { export function TOTAL_TABLES_SELECTED(selected: number, total: number): string {
return localize('total.tables.selected.of.total', "{0} of {1}", formatNumber(selected), formatNumber(total)); return localize('total.tables.selected.of.total', "{0} of {1}", formatNumber(selected), formatNumber(total));

View File

@@ -401,7 +401,11 @@ export class CreateSqlMigrationServiceDialog {
constants.RESOURCE_GROUP_NOT_FOUND); constants.RESOURCE_GROUP_NOT_FOUND);
const selectedResourceGroupValue = this.migrationServiceResourceGroupDropdown.values.find(v => v.name.toLowerCase() === this._resourceGroupPreset.toLowerCase()); const selectedResourceGroupValue = this.migrationServiceResourceGroupDropdown.values.find(v => v.name.toLowerCase() === this._resourceGroupPreset.toLowerCase());
this.migrationServiceResourceGroupDropdown.value = (selectedResourceGroupValue) ? selectedResourceGroupValue : this.migrationServiceResourceGroupDropdown.values[0]; this.migrationServiceResourceGroupDropdown.value = (selectedResourceGroupValue)
? selectedResourceGroupValue
: this.migrationServiceResourceGroupDropdown.values?.length > 0
? this.migrationServiceResourceGroupDropdown.values[0]
: '';
} finally { } finally {
this.migrationServiceResourceGroupDropdown.loading = false; this.migrationServiceResourceGroupDropdown.loading = false;
} }

View File

@@ -9,6 +9,8 @@ import * as constants from '../../constants/strings';
import { AzureSqlDatabaseServer } from '../../api/azure'; import { AzureSqlDatabaseServer } from '../../api/azure';
import { collectSourceDatabaseTableInfo, collectTargetDatabaseTableInfo, TableInfo } from '../../api/sqlUtils'; import { collectSourceDatabaseTableInfo, collectTargetDatabaseTableInfo, TableInfo } from '../../api/sqlUtils';
import { MigrationStateModel } from '../../models/stateMachine'; import { MigrationStateModel } from '../../models/stateMachine';
import { IconPathHelper } from '../../constants/iconPathHelper';
import { Tab } from 'azdata';
import { updateControlDisplay } from '../../api/utils'; import { updateControlDisplay } from '../../api/utils';
const DialogName = 'TableMigrationSelection'; const DialogName = 'TableMigrationSelection';
@@ -16,10 +18,11 @@ const DialogName = 'TableMigrationSelection';
export class TableMigrationSelectionDialog { export class TableMigrationSelectionDialog {
private _dialog: azdata.window.Dialog | undefined; private _dialog: azdata.window.Dialog | undefined;
private _headingText!: azdata.TextComponent; private _headingText!: azdata.TextComponent;
private _missingTablesText!: azdata.TextComponent; private _refreshButton!: azdata.ButtonComponent;
private _filterInputBox!: azdata.InputBoxComponent; private _filterInputBox!: azdata.InputBoxComponent;
private _tableSelectionTable!: azdata.TableComponent; private _tableSelectionTable!: azdata.TableComponent;
private _tableLoader!: azdata.LoadingComponent; private _missingTargetTablesTable!: azdata.TableComponent;
private _refreshLoader!: azdata.LoadingComponent;
private _disposables: vscode.Disposable[] = []; private _disposables: vscode.Disposable[] = [];
private _isOpen: boolean = false; private _isOpen: boolean = false;
private _model: MigrationStateModel; private _model: MigrationStateModel;
@@ -28,6 +31,9 @@ export class TableMigrationSelectionDialog {
private _targetTableMap!: Map<string, TableInfo>; private _targetTableMap!: Map<string, TableInfo>;
private _onSaveCallback: () => Promise<void>; private _onSaveCallback: () => Promise<void>;
private _missingTableCount: number = 0; private _missingTableCount: number = 0;
private _selectableTablesTab!: Tab;
private _missingTablesTab!: Tab;
private _tabs!: azdata.TabbedPanelComponent;
constructor( constructor(
model: MigrationStateModel, model: MigrationStateModel,
@@ -41,7 +47,12 @@ export class TableMigrationSelectionDialog {
private async _loadData(): Promise<void> { private async _loadData(): Promise<void> {
try { try {
this._tableLoader.loading = true; this._refreshLoader.loading = true;
this._updateRowSelection();
await updateControlDisplay(this._tableSelectionTable, false);
await updateControlDisplay(this._missingTargetTablesTable, false);
const targetDatabaseInfo = this._model._sourceTargetMapping.get(this._sourceDatabaseName); const targetDatabaseInfo = this._model._sourceTargetMapping.get(this._sourceDatabaseName);
if (targetDatabaseInfo) { if (targetDatabaseInfo) {
const sourceTableList: TableInfo[] = await collectSourceDatabaseTableInfo( const sourceTableList: TableInfo[] = await collectSourceDatabaseTableInfo(
@@ -86,6 +97,7 @@ export class TableMigrationSelectionDialog {
this._tableSelectionMap.set(table.tableName, tableInfo); this._tableSelectionMap.set(table.tableName, tableInfo);
}); });
} }
this._dialog!.message = { text: '', level: azdata.window.MessageLevel.Information };
} catch (error) { } catch (error) {
this._dialog!.message = { this._dialog!.message = {
text: constants.DATABASE_TABLE_CONNECTION_ERROR, text: constants.DATABASE_TABLE_CONNECTION_ERROR,
@@ -93,13 +105,16 @@ export class TableMigrationSelectionDialog {
level: azdata.window.MessageLevel.Error level: azdata.window.MessageLevel.Error
}; };
} finally { } finally {
this._tableLoader.loading = false; this._refreshLoader.loading = false;
await updateControlDisplay(this._tableSelectionTable, true, 'flex');
await updateControlDisplay(this._missingTargetTablesTable, true, 'flex');
await this._loadControls(); await this._loadControls();
} }
} }
private async _loadControls(): Promise<void> { private async _loadControls(): Promise<void> {
const data: any[][] = []; const data: any[][] = [];
const missingData: any[][] = [];
const filterText = this._filterInputBox.value ?? ''; const filterText = this._filterInputBox.value ?? '';
const selectedItems: number[] = []; const selectedItems: number[] = [];
let tableRow = 0; let tableRow = 0;
@@ -125,67 +140,45 @@ export class TableMigrationSelectionDialog {
} }
tableRow++; tableRow++;
} else {
this._missingTableCount++;
missingData.push([sourceTable.tableName]);
} }
this._missingTableCount += targetTable ? 0 : 1;
} }
}); });
await this._tableSelectionTable.updateProperty('data', data); await this._tableSelectionTable.updateProperty('data', data);
this._tableSelectionTable.selectedRows = selectedItems; this._tableSelectionTable.selectedRows = selectedItems;
await this._updateRowSelection(); await this._missingTargetTablesTable.updateProperty('data', missingData);
this._updateRowSelection();
if (this._missingTableCount > 0 && this._tabs.items.length === 1) {
this._tabs.updateTabs([this._selectableTablesTab, this._missingTablesTab]);
}
} }
private async _initializeDialog(dialog: azdata.window.Dialog): Promise<void> { private async _initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
dialog.registerContent(async (view) => { const tab = azdata.window.createTab('');
this._filterInputBox = view.modelBuilder.inputBox() tab.registerContent(async (view) => {
.withProps({
inputType: 'search',
placeHolder: constants.TABLE_SELECTION_FILTER,
width: 268,
}).component();
this._disposables.push( this._tabs = view.modelBuilder.tabbedPanel()
this._filterInputBox.onTextChanged( .withTabs([])
async e => await this._loadControls()));
this._headingText = view.modelBuilder.text()
.withProps({ value: constants.DATABASE_LOADING_TABLES })
.component(); .component();
this._missingTablesText = view.modelBuilder.text() await this._createSelectableTablesTab(view);
.withProps({ display: 'none' }) await this._createMissingTablesTab(view);
.component();
this._tableSelectionTable = await this._createSelectionTable(view); this._tabs.updateTabs([this._selectableTablesTab]);
this._tableLoader = view.modelBuilder.loadingComponent()
.withItem(this._tableSelectionTable)
.withProps({
loading: false,
loadingText: constants.DATABASE_TABLE_DATA_LOADING
}).component();
const flex = view.modelBuilder.flexContainer()
.withItems([
this._filterInputBox,
this._headingText,
this._missingTablesText,
this._tableLoader],
{ flex: '0 0 auto' })
.withProps({ CSSStyles: { 'margin': '0 0 0 15px' } })
.withLayout({
flexFlow: 'column',
height: '100%',
width: 565,
}).component();
this._disposables.push( this._disposables.push(
view.onClosed(e => view.onClosed(e =>
this._disposables.forEach( this._disposables.forEach(
d => { try { d.dispose(); } catch { } }))); d => { try { d.dispose(); } catch { } })));
await view.initializeModel(flex); await view.initializeModel(this._tabs);
await this._loadData(); await this._loadData();
}); });
dialog.content = [tab];
} }
public async openDialog(dialogTitle: string) { public async openDialog(dialogTitle: string) {
@@ -194,7 +187,7 @@ export class TableMigrationSelectionDialog {
this._dialog = azdata.window.createModelViewDialog( this._dialog = azdata.window.createModelViewDialog(
dialogTitle, dialogTitle,
DialogName, DialogName,
600); 600, undefined, undefined, false);
this._dialog.okButton.label = constants.TABLE_SELECTION_UPDATE_BUTTON; this._dialog.okButton.label = constants.TABLE_SELECTION_UPDATE_BUTTON;
this._dialog.okButton.position = 'left'; this._dialog.okButton.position = 'left';
@@ -214,13 +207,105 @@ export class TableMigrationSelectionDialog {
} }
} }
private async _createSelectionTable(view: azdata.ModelView): Promise<azdata.TableComponent> { private async _createSelectableTablesTab(view: azdata.ModelView): Promise<void> {
this._headingText = view.modelBuilder.text()
.withProps({ value: constants.DATABASE_LOADING_TABLES })
.component();
this._filterInputBox = view.modelBuilder.inputBox()
.withProps({
inputType: 'search',
placeHolder: constants.TABLE_SELECTION_FILTER,
width: 268,
}).component();
this._disposables.push(
this._filterInputBox.onTextChanged(
async e => await this._loadControls()));
this._refreshButton = view.modelBuilder.button()
.withProps({
buttonType: azdata.ButtonType.Normal,
iconHeight: 16,
iconWidth: 16,
iconPath: IconPathHelper.refresh,
label: constants.DATABASE_TABLE_REFRESH_LABEL,
width: 70,
CSSStyles: { 'margin': '5px 0 0 15px' },
})
.component();
this._disposables.push(
this._refreshButton.onDidClick(
async e => await this._loadData()));
this._refreshLoader = view.modelBuilder.loadingComponent()
.withItem(this._refreshButton)
.withProps({
loading: false,
CSSStyles: { 'height': '8px', 'margin': '5px 0 0 15px' }
})
.component();
const flexTopRow = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
flexWrap: 'wrap',
})
.component();
flexTopRow.addItem(this._filterInputBox, { flex: '0 0 auto' });
flexTopRow.addItem(this._refreshLoader, { flex: '0 0 auto' });
this._tableSelectionTable = this._createSelectionTable(view);
const flex = view.modelBuilder.flexContainer()
.withItems([
flexTopRow,
this._headingText,
this._tableSelectionTable],
{ flex: '0 0 auto' })
.withProps({ CSSStyles: { 'margin': '10px 0 0 15px' } })
.withLayout({
flexFlow: 'column',
height: '100%',
width: 550,
}).component();
this._selectableTablesTab = {
content: flex,
id: 'tableSelectionTab',
title: constants.SELECT_TABLES_FOR_MIGRATION,
};
}
private async _createMissingTablesTab(view: azdata.ModelView): Promise<void> {
this._missingTargetTablesTable = this._createMissingTablesTable(view);
const flex = view.modelBuilder.flexContainer()
.withItems(
[this._missingTargetTablesTable],
{ flex: '0 0 auto' })
.withProps({ CSSStyles: { 'margin': '10px 0 0 15px' } })
.withLayout({
flexFlow: 'column',
height: '100%',
width: 550,
}).component();
this._missingTablesTab = {
content: flex,
id: 'missingTablesTab',
title: constants.MISSING_TARGET_TABLES_COUNT(this._missingTableCount),
};
}
private _createSelectionTable(view: azdata.ModelView): azdata.TableComponent {
const cssClass = 'no-borders'; const cssClass = 'no-borders';
const table = view.modelBuilder.table() const table = view.modelBuilder.table()
.withProps({ .withProps({
data: [], data: [],
width: 565, width: 550,
height: '600px', height: '600px',
display: 'flex',
forceFitColumns: azdata.ColumnSizingMode.ForceFit, forceFitColumns: azdata.ColumnSizingMode.ForceFit,
columns: [ columns: [
<azdata.CheckboxColumn>{ <azdata.CheckboxColumn>{
@@ -236,7 +321,7 @@ export class TableMigrationSelectionDialog {
name: constants.TABLE_SELECTION_TABLENAME_COLUMN, name: constants.TABLE_SELECTION_TABLENAME_COLUMN,
value: 'tableName', value: 'tableName',
type: azdata.ColumnType.text, type: azdata.ColumnType.text,
width: 300, width: 285,
cssClass: cssClass, cssClass: cssClass,
headerCssClass: cssClass, headerCssClass: cssClass,
}, },
@@ -275,23 +360,45 @@ export class TableMigrationSelectionDialog {
} }
}); });
await this._updateRowSelection(); this._updateRowSelection();
})); }));
return table; return table;
} }
private async _updateRowSelection(): Promise<void> { private _createMissingTablesTable(view: azdata.ModelView): azdata.TableComponent {
this._headingText.value = this._tableSelectionTable.data.length > 0 const cssClass = 'no-borders';
? constants.TABLE_SELECTED_COUNT( const table = view.modelBuilder.table()
this._tableSelectionTable.selectedRows?.length ?? 0, .withProps({
this._tableSelectionTable.data.length) data: [],
: this._tableLoader.loading width: 550,
? constants.DATABASE_LOADING_TABLES height: '600px',
display: 'flex',
forceFitColumns: azdata.ColumnSizingMode.ForceFit,
columns: [{
name: constants.MISSING_TABLE_NAME_COLUMN,
value: 'tableName',
type: azdata.ColumnType.text,
cssClass: cssClass,
headerCssClass: cssClass,
}],
})
.withValidation(() => true)
.component();
return table;
}
private _updateRowSelection(): void {
this._headingText.value = this._refreshLoader.loading
? constants.DATABASE_LOADING_TABLES
: this._tableSelectionTable.data?.length > 0
? constants.TABLE_SELECTED_COUNT(
this._tableSelectionTable.selectedRows?.length ?? 0,
this._tableSelectionTable.data?.length ?? 0)
: constants.DATABASE_MISSING_TABLES; : constants.DATABASE_MISSING_TABLES;
this._missingTablesText.value = constants.MISSING_TARGET_TABLES_COUNT(this._missingTableCount); this._missingTablesTab.title = constants.MISSING_TARGET_TABLES_COUNT(this._missingTableCount);
await updateControlDisplay(this._missingTablesText, this._missingTableCount > 0);
} }
private async _save(): Promise<void> { private async _save(): Promise<void> {
@@ -303,7 +410,7 @@ export class TableMigrationSelectionDialog {
selectedRows.forEach(rowIndex => { selectedRows.forEach(rowIndex => {
const tableRow = this._tableSelectionTable.data[rowIndex]; const tableRow = this._tableSelectionTable.data[rowIndex];
const tableName = tableRow.length > 1 const tableName = tableRow.length > 1
? this._tableSelectionTable.data[rowIndex][1] as string ? tableRow[1] as string
: ''; : '';
const tableInfo = this._tableSelectionMap.get(tableName); const tableInfo = this._tableSelectionMap.get(tableName);
if (tableInfo) { if (tableInfo) {

View File

@@ -97,12 +97,15 @@ export function sendSqlMigrationActionEvent(telemetryView: TelemetryViews, telem
} }
export function getTelemetryProps(migrationStateModel: MigrationStateModel): TelemetryEventProperties { export function getTelemetryProps(migrationStateModel: MigrationStateModel): TelemetryEventProperties {
const tenantId = migrationStateModel._azureAccount?.properties?.tenants?.length > 0
? migrationStateModel._azureAccount?.properties?.tenants[0]?.id
: '';
return { return {
'sessionId': migrationStateModel._sessionId, 'sessionId': migrationStateModel._sessionId,
'subscriptionId': migrationStateModel._targetSubscription?.id, 'subscriptionId': migrationStateModel._targetSubscription?.id,
'resourceGroup': migrationStateModel._resourceGroup?.name, 'resourceGroup': migrationStateModel._resourceGroup?.name,
'targetType': migrationStateModel._targetType, 'targetType': migrationStateModel._targetType,
'tenantId': migrationStateModel._azureAccount?.properties?.tenants[0]?.id, 'tenantId': tenantId,
}; };
} }

View File

@@ -897,14 +897,16 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._sqlSourceUsernameInput.value = username; this._sqlSourceUsernameInput.value = username;
this._sqlSourcePassword.value = (await getSourceConnectionCredentials()).password; this._sqlSourcePassword.value = (await getSourceConnectionCredentials()).password;
this._windowsUserAccountText.value = const networkShares = this.migrationStateModel._databaseBackup?.networkShares?.length > 0
this.migrationStateModel._databaseBackup.networkShares[0]?.windowsUser ? this.migrationStateModel._databaseBackup?.networkShares
?? this.migrationStateModel.savedInfo?.networkShares[0]?.windowsUser : this.migrationStateModel.savedInfo?.networkShares ?? [];
?? '';
this._passwordText.value = const networkShare = networkShares?.length > 0
this.migrationStateModel._databaseBackup.networkShares[0]?.password ? networkShares[0]
?? this.migrationStateModel.savedInfo?.networkShares[0]?.password : undefined;
?? '';
this._windowsUserAccountText.value = networkShare?.windowsUser ?? '';
this._passwordText.value = networkShare?.password ?? '';
this._networkShareTargetDatabaseNames = []; this._networkShareTargetDatabaseNames = [];
this._networkShareLocations = []; this._networkShareLocations = [];
@@ -1379,13 +1381,15 @@ export class DatabaseBackupPage extends MigrationWizardPage {
break; break;
case NetworkContainerType.NETWORK_SHARE: case NetworkContainerType.NETWORK_SHARE:
// All network share migrations use the same storage account // All network share migrations use the same storage account
const storageAccount = this.migrationStateModel._databaseBackup.networkShares[0]?.storageAccount; if (this.migrationStateModel._databaseBackup.networkShares?.length > 0) {
const storageKey = (await getStorageAccountAccessKeys( const storageAccount = this.migrationStateModel._databaseBackup.networkShares[0]?.storageAccount;
this.migrationStateModel._azureAccount, const storageKey = (await getStorageAccountAccessKeys(
this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._azureAccount,
storageAccount)).keyName1; this.migrationStateModel._databaseBackup.subscription,
for (let i = 0; i < this.migrationStateModel._databaseBackup.networkShares.length; i++) { storageAccount)).keyName1;
this.migrationStateModel._databaseBackup.networkShares[i].storageKey = storageKey; for (let i = 0; i < this.migrationStateModel._databaseBackup.networkShares.length; i++) {
this.migrationStateModel._databaseBackup.networkShares[i].storageKey = storageKey;
}
} }
break; break;
} }

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage'; import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationMode, MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine'; import { MigrationMode, MigrationStateModel, NetworkContainerType, NetworkShare, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings'; import * as constants from '../constants/strings';
import { createHeadingTextComponent, createInformationRow, createLabelTextComponent } from './wizardController'; import { createHeadingTextComponent, createInformationRow, createLabelTextComponent } from './wizardController';
import { getResourceGroupFromId } from '../api/azure'; import { getResourceGroupFromId } from '../api/azure';
@@ -185,7 +185,10 @@ export class SummaryPage extends MigrationWizardPage {
.withLayout({ flexFlow: 'column' }) .withLayout({ flexFlow: 'column' })
.component(); .component();
const networkShare = this.migrationStateModel._databaseBackup.networkShares[0]; const networkShare = this.migrationStateModel._databaseBackup.networkShares?.length > 0
? this.migrationStateModel._databaseBackup.networkShares[0]
: <NetworkShare>{};
switch (this.migrationStateModel._databaseBackup.networkContainerType) { switch (this.migrationStateModel._databaseBackup.networkContainerType) {
case NetworkContainerType.NETWORK_SHARE: case NetworkContainerType.NETWORK_SHARE:
flexContainer.addItems([ flexContainer.addItems([