mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-26 01:25:38 -05:00
Adding blob storage support (WIP) (#15477)
Bumping version Fixing cancel migration bug
This commit is contained in:
@@ -347,11 +347,11 @@ export interface StartDatabaseMigrationRequest {
|
||||
sourceDatabaseName: string,
|
||||
migrationService: string,
|
||||
backupConfiguration: {
|
||||
targetLocation: {
|
||||
targetLocation?: {
|
||||
storageAccountResourceId: string,
|
||||
accountKey: string,
|
||||
},
|
||||
sourceLocation: SourceLocation
|
||||
sourceLocation?: SourceLocation
|
||||
},
|
||||
sourceSqlConnection: {
|
||||
authentication: string,
|
||||
@@ -416,8 +416,8 @@ export interface SqlConnectionInfo {
|
||||
}
|
||||
|
||||
export interface BackupConfiguration {
|
||||
sourceLocation: SourceLocation;
|
||||
targetLocation: TargetLocation;
|
||||
sourceLocation?: SourceLocation;
|
||||
targetLocation?: TargetLocation;
|
||||
}
|
||||
|
||||
export interface AutoCutoverConfiguration {
|
||||
@@ -454,7 +454,7 @@ export interface TargetLocation {
|
||||
|
||||
export interface BackupFileInfo {
|
||||
fileName: string;
|
||||
status: string;
|
||||
status: 'Arrived' | 'Uploading' | 'Uploaded' | 'Restoring' | 'Restored' | 'Cancelled' | 'Ignored';
|
||||
}
|
||||
|
||||
export interface DatabaseMigrationFileShare {
|
||||
|
||||
@@ -51,7 +51,8 @@ export const VIEW_SELECT_BUTTON_LABEL = localize('sql.migration.view.select.butt
|
||||
export function TOTAL_DATABASES_SELECTED(selectedDbCount: number, totalDbCount: number): string {
|
||||
return localize('total.databases.selected', "{0} of {1} Database(s) selected.", selectedDbCount, totalDbCount);
|
||||
}
|
||||
|
||||
export const SELECT_TARGET_TO_CONTINUE = localize('sql.migration.select.target.to.continue', "Please select a target to continue");
|
||||
export const SELECT_DATABASE_TO_MIGRATE = localize('sql.migration.select.database.to.migrate', "Please select databases to migrate");
|
||||
export const ASSESSMENT_COMPLETED = (serverName: string): string => {
|
||||
return localize('sql.migration.generic.congratulations', "We have completed the assessment of your SQL Server Instance '{0}'.", serverName);
|
||||
};
|
||||
@@ -83,6 +84,8 @@ export const DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL = localize('sql.migrat
|
||||
export const DATABASE_BACKUP_NC_BLOB_STORAGE_RADIO_LABEL = localize('sql.migration.nc.blob.storage.radio.label', "My database backups are in an Azure Storage Blob Container (Coming soon)");
|
||||
export const DATABASE_BACKUP_NC_FILE_SHARE_RADIO_LABEL = localize('sql.migration.nc.file.share.radio.label', "My database backups are in an Azure Storage File Share (Coming soon)");
|
||||
|
||||
|
||||
|
||||
export const DATABASE_BACKUP_NETWORK_SHARE_HEADER_TEXT = localize('sql.migration.network.share.header.text', "Network share details");
|
||||
export const DATABASE_BACKUP_NC_NETWORK_SHARE_HELP_TEXT = localize('sql.migration.network.share.help.text', "Provide the network share location that contains backups and the user credentials that has read access to the share");
|
||||
export const DATABASE_BACKUP_NETWORK_SHARE_LOCATION_LABEL = localize('sql.migration.network.share.location.label', "Network share location that contains backups.");
|
||||
@@ -97,8 +100,8 @@ export const DATABASE_BACKUP_SUBSCRIPTION_PLACEHOLDER = localize('sql.migration.
|
||||
export const DATABASE_BACKUP_NETWORK_SHARE_NETWORK_STORAGE_ACCOUNT_LABEL = localize('sql.migration.network.share.storage.account.label', "Select the storage account where backup files will be copied.");
|
||||
export const DATABASE_BACKUP_STORAGE_ACCOUNT_PLACEHOLDER = localize('sql.migration.network.share.storage.account.placeholder', "Select account");
|
||||
export const DUPLICATE_NAME_ERROR = localize('sql.migration.unique.name', "Select a unique name for this target database");
|
||||
export function DATABASE_ALREADY_EXISTS_MI(targetName: string): string {
|
||||
return localize('sql.migration.database.already.exists', "Database with the same name already exists on target Managed Instance '{0}'", targetName);
|
||||
export function DATABASE_ALREADY_EXISTS_MI(dbName: string, targetName: string): string {
|
||||
return localize('sql.migration.database.already.exists', "Database '{0}' already exists on target Managed Instance '{1}'.", dbName, targetName);
|
||||
}
|
||||
export const DATABASE_BACKUP_BLOB_STORAGE_SUBSCRIPTION_LABEL = localize('sql.migration.blob.storage.subscription.label', "Select the subscription that contains the storage account.");
|
||||
export const DATABASE_BACKUP_BLOB_STORAGE_ACCOUNT_LABEL = localize('sql.migration.blob.storage.account.label', "Select the storage account that contains the backup files.");
|
||||
@@ -128,9 +131,6 @@ export const INVALID_FILESHARE_ERROR = localize('sql.migration.invalid.fileShare
|
||||
export const INVALID_BLOBCONTAINER_ERROR = localize('sql.migration.invalid.blobContainer.error', "Please select a valid blob container to proceed.");
|
||||
export const INVALID_NETWORK_SHARE_LOCATION = localize('sql.migration.invalid.network.share.location', "Invalid network share location format. Example: {0}", '\\\\Servername.domainname.com\\Backupfolder');
|
||||
export const INVALID_USER_ACCOUNT = localize('sql.migration.invalid.user.account', "Invalid user account format. Example: {0}", 'Domain\\username');
|
||||
export function TARGET_NAME_FOR_DATABASE(dbName: string): string {
|
||||
return localize('sql.migration.target.name.for.database', 'Target name for database ‘{0}’', dbName);
|
||||
}
|
||||
export function TARGET_NETWORK_SHARE_LOCATION(dbName: string): string {
|
||||
return localize('sql.migration.network.share.location', "Network share location to read backups for database ‘{0}’", dbName);
|
||||
}
|
||||
@@ -236,6 +236,7 @@ export const MODE = localize('sql.migration.mode', "Mode");
|
||||
export const BACKUP_LOCATION = localize('sql.migration.backup.location', "Backup Location");
|
||||
export const AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS = localize('sql.migration.azure.storage.account.to.upload.backups', "Azure Storage Account to Upload Backups");
|
||||
export const SHIR = localize('sql.migration.shir', "Self-hosted Integration Runtime node");
|
||||
export const TARGET_NAME = localize('sql.migration.summary.target.name', "Target Databases:");
|
||||
|
||||
// Open notebook quick pick string
|
||||
export const NOTEBOOK_QUICK_PICK_PLACEHOLDER = localize('sql.migration.quick.pick.placeholder', "Select the operation you'd like to perform");
|
||||
|
||||
@@ -538,7 +538,11 @@ export class MigrationCutoverDialog {
|
||||
});
|
||||
|
||||
if (migrationStatusTextValue === MigrationStatus.InProgress) {
|
||||
this._cutoverButton.enabled = tableData.length > 0;
|
||||
const restoredCount = (this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets.filter(a => a.listOfBackupFiles[0].status === 'Restored'))?.length!;
|
||||
if (restoredCount > 0) {
|
||||
this._cutoverButton.enabled = true;
|
||||
}
|
||||
this._cancelButton.enabled = true;
|
||||
} else {
|
||||
this._cutoverButton.enabled = false;
|
||||
this._cancelButton.enabled = false;
|
||||
|
||||
@@ -28,11 +28,13 @@ export class MigrationLocalStorage {
|
||||
if (refreshStatus) {
|
||||
try {
|
||||
const backupConfiguration = migration.migrationContext.properties.backupConfiguration;
|
||||
const sourceDatabase = migration.migrationContext.properties.sourceDatabaseName;
|
||||
migration.migrationContext = await getMigrationStatus(
|
||||
migration.azureAccount,
|
||||
migration.subscription,
|
||||
migration.migrationContext
|
||||
);
|
||||
migration.migrationContext.properties.sourceDatabaseName = sourceDatabase;
|
||||
migration.migrationContext.properties.backupConfiguration = backupConfiguration;
|
||||
if (migration.asyncUrl) {
|
||||
migration.asyncOperationResult = await getMigrationAsyncOperationDetails(
|
||||
|
||||
@@ -55,26 +55,29 @@ export enum NetworkContainerType {
|
||||
NETWORK_SHARE
|
||||
}
|
||||
|
||||
export interface DatabaseBackupModel {
|
||||
migrationMode: MigrationMode;
|
||||
networkContainerType: NetworkContainerType;
|
||||
storageKey: string;
|
||||
networkShare: NetworkShare;
|
||||
subscription: azureResource.AzureResourceSubscription;
|
||||
blob: Blob;
|
||||
}
|
||||
|
||||
export interface NetworkShare {
|
||||
networkShareLocation: string;
|
||||
windowsUser: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface DatabaseBackupModel {
|
||||
migrationMode: MigrationMode;
|
||||
networkContainerType: NetworkContainerType;
|
||||
networkShareLocation: string;
|
||||
windowsUser: string;
|
||||
password: string;
|
||||
subscription: azureResource.AzureResourceSubscription;
|
||||
resourceGroup: azureResource.AzureResourceResourceGroup;
|
||||
storageAccount: StorageAccount;
|
||||
storageKey: string;
|
||||
azureSecurityToken: string;
|
||||
fileShares: azureResource.FileShare[];
|
||||
blobContainers: azureResource.BlobContainer[];
|
||||
}
|
||||
|
||||
export interface Blob {
|
||||
resourceGroup: azureResource.AzureResourceResourceGroup;
|
||||
storageAccount: StorageAccount;
|
||||
blobContainer: azureResource.BlobContainer;
|
||||
}
|
||||
|
||||
export interface Model {
|
||||
readonly sourceConnectionId: string;
|
||||
readonly currentState: State;
|
||||
@@ -140,6 +143,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
) {
|
||||
this._currentState = State.INIT;
|
||||
this._databaseBackup = {} as DatabaseBackupModel;
|
||||
this._databaseBackup.networkShare = {} as NetworkShare;
|
||||
this._databaseBackup.blob = {} as Blob;
|
||||
}
|
||||
|
||||
public get sourceConnectionId(): string {
|
||||
@@ -490,15 +495,15 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
return this._targetSqlVirtualMachines[index];
|
||||
}
|
||||
|
||||
public async getStorageAccountValues(subscription: azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
|
||||
public async getStorageAccountValues(subscription: azureResource.AzureResourceSubscription, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> {
|
||||
let storageAccountValues: azdata.CategoryValue[] = [];
|
||||
if (!this._databaseBackup.resourceGroup) {
|
||||
if (!resourceGroup) {
|
||||
return storageAccountValues;
|
||||
}
|
||||
try {
|
||||
const storageAccount = (await getAvailableStorageAccounts(this._azureAccount, subscription));
|
||||
this._storageAccounts = storageAccount.filter(sa => {
|
||||
return sa.location.toLowerCase() === this._targetServerInstance.location.toLowerCase() && sa.resourceGroup?.toLowerCase() === this._databaseBackup.resourceGroup.name.toLowerCase();
|
||||
return sa.location.toLowerCase() === this._targetServerInstance.location.toLowerCase() && sa.resourceGroup?.toLowerCase() === resourceGroup.name.toLowerCase();
|
||||
});
|
||||
this._storageAccounts.forEach((storageAccount) => {
|
||||
storageAccountValues.push({
|
||||
@@ -652,19 +657,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
properties: {
|
||||
sourceDatabaseName: '',
|
||||
migrationService: this._sqlMigrationService?.id!,
|
||||
backupConfiguration: {
|
||||
targetLocation: {
|
||||
storageAccountResourceId: this._databaseBackup.storageAccount.id,
|
||||
accountKey: this._databaseBackup.storageKey,
|
||||
},
|
||||
sourceLocation: {
|
||||
fileShare: {
|
||||
path: this._databaseBackup.networkShareLocation,
|
||||
username: this._databaseBackup.windowsUser,
|
||||
password: this._databaseBackup.password,
|
||||
}
|
||||
},
|
||||
},
|
||||
backupConfiguration: {},
|
||||
sourceSqlConnection: {
|
||||
dataSource: currentConnection?.serverName!,
|
||||
authentication: this._authenticationType,
|
||||
@@ -674,11 +667,39 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
scope: this._targetServerInstance.id
|
||||
}
|
||||
};
|
||||
switch (this._databaseBackup.networkContainerType) {
|
||||
case NetworkContainerType.BLOB_CONTAINER:
|
||||
requestBody.properties.backupConfiguration = {
|
||||
targetLocation: undefined!,
|
||||
sourceLocation: {
|
||||
azureBlob: {
|
||||
storageAccountResourceId: this._databaseBackup.blob.storageAccount.id,
|
||||
accountKey: this._databaseBackup.storageKey,
|
||||
blobContainerName: this._databaseBackup.blob.blobContainer.name
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case NetworkContainerType.NETWORK_SHARE:
|
||||
requestBody.properties.backupConfiguration = {
|
||||
targetLocation: {
|
||||
storageAccountResourceId: this._databaseBackup.networkShare.storageAccount.id,
|
||||
accountKey: this._databaseBackup.storageKey,
|
||||
},
|
||||
sourceLocation: {
|
||||
fileShare: {
|
||||
path: this._databaseBackup.networkShare.networkShareLocation,
|
||||
username: this._databaseBackup.networkShare.windowsUser,
|
||||
password: this._databaseBackup.networkShare.password,
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._migrationDbs.length; i++) {
|
||||
requestBody.properties.sourceDatabaseName = this._migrationDbs[i];
|
||||
try {
|
||||
requestBody.properties.backupConfiguration.sourceLocation.fileShare!.path = this._databaseBackup.networkShareLocation;
|
||||
requestBody.properties.sourceDatabaseName = this._migrationDbs[i];
|
||||
const response = await startDatabaseMigration(
|
||||
this._azureAccount,
|
||||
this._targetSubscription,
|
||||
@@ -687,6 +708,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
this._targetDatabaseNames[i],
|
||||
requestBody
|
||||
);
|
||||
response.databaseMigration.properties.sourceDatabaseName = this._migrationDbs[i];
|
||||
response.databaseMigration.properties.backupConfiguration = requestBody.properties.backupConfiguration!;
|
||||
if (response.status === 201 || response.status === 200) {
|
||||
MigrationLocalStorage.saveMigration(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
||||
private _refresh1!: azdata.ButtonComponent;
|
||||
private _refresh2!: azdata.ButtonComponent;
|
||||
|
||||
private _firstEnter: boolean = true;
|
||||
|
||||
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
|
||||
super(wizard, azdata.window.createWizardPage(constants.IR_PAGE_TITLE), migrationStateModel);
|
||||
@@ -75,7 +76,10 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
||||
}
|
||||
|
||||
public async onPageEnter(): Promise<void> {
|
||||
this.populateMigrationService();
|
||||
if (this._firstEnter) {
|
||||
this.populateMigrationService();
|
||||
this._firstEnter = false;
|
||||
}
|
||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
||||
this.wizard.message = {
|
||||
|
||||
@@ -45,6 +45,7 @@ export class MigrationModePage extends MigrationWizardPage {
|
||||
label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
|
||||
name: buttonGroup,
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'font-weight': 'bold'
|
||||
},
|
||||
checked: true
|
||||
@@ -53,6 +54,7 @@ export class MigrationModePage extends MigrationWizardPage {
|
||||
const onlineDescription = view.modelBuilder.text().withProps({
|
||||
value: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION,
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'margin': '0 0 10px 20px'
|
||||
}
|
||||
}).component();
|
||||
@@ -69,6 +71,7 @@ export class MigrationModePage extends MigrationWizardPage {
|
||||
label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
|
||||
name: buttonGroup,
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'font-weight': 'bold'
|
||||
},
|
||||
}).component();
|
||||
@@ -76,6 +79,7 @@ export class MigrationModePage extends MigrationWizardPage {
|
||||
const offlineDescription = view.modelBuilder.text().withProps({
|
||||
value: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_DESCRIPTION,
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'margin': '0 0 10px 20px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
@@ -477,9 +477,12 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
||||
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
||||
return true;
|
||||
}
|
||||
if (this.migrationStateModel._migrationDbs.length === 0) {
|
||||
errors.push('Please select databases to migrate');
|
||||
|
||||
if (this._rbg.selectedCardId === undefined || this._rbg.selectedCardId === '') {
|
||||
errors.push(constants.SELECT_TARGET_TO_CONTINUE);
|
||||
}
|
||||
if (this.migrationStateModel._migrationDbs.length === 0) {
|
||||
errors.push(constants.SELECT_DATABASE_TO_MIGRATE);
|
||||
}
|
||||
if ((<azdata.CategoryValue>this._managedInstanceSubscriptionDropdown.value)?.displayName === constants.NO_SUBSCRIPTIONS_FOUND) {
|
||||
errors.push(constants.INVALID_SUBSCRIPTION_ERROR);
|
||||
|
||||
@@ -86,46 +86,40 @@ export class SummaryPage extends MigrationWizardPage {
|
||||
flexContainer.addItems(
|
||||
[
|
||||
createInformationRow(this._view, constants.BACKUP_LOCATION, constants.NETWORK_SHARE),
|
||||
createInformationRow(this._view, constants.NETWORK_SHARE, this.migrationStateModel._databaseBackup.networkShareLocation),
|
||||
createInformationRow(this._view, constants.USER_ACCOUNT, this.migrationStateModel._databaseBackup.windowsUser),
|
||||
createInformationRow(this._view, constants.NETWORK_SHARE, this.migrationStateModel._databaseBackup.networkShare.networkShareLocation),
|
||||
createInformationRow(this._view, constants.USER_ACCOUNT, this.migrationStateModel._databaseBackup.networkShare.windowsUser),
|
||||
createHeadingTextComponent(this._view, constants.AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS),
|
||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
|
||||
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.storageAccount.location),
|
||||
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.storageAccount.resourceGroup!),
|
||||
createInformationRow(this._view, constants.STORAGE_ACCOUNT, this.migrationStateModel._databaseBackup.storageAccount.name!),
|
||||
createHeadingTextComponent(this._view, 'Target Databases:')
|
||||
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.networkShare.storageAccount.location),
|
||||
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.networkShare.storageAccount.resourceGroup!),
|
||||
createInformationRow(this._view, constants.STORAGE_ACCOUNT, this.migrationStateModel._databaseBackup.networkShare.storageAccount.name!),
|
||||
]
|
||||
);
|
||||
this.migrationStateModel._migrationDbs.forEach((db, index) => {
|
||||
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_NAME_FOR_DATABASE(db), this.migrationStateModel._targetDatabaseNames[index]));
|
||||
});
|
||||
break;
|
||||
case NetworkContainerType.FILE_SHARE:
|
||||
flexContainer.addItems(
|
||||
[
|
||||
createInformationRow(this._view, constants.TYPE, constants.FILE_SHARE),
|
||||
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
|
||||
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.storageAccount.name),
|
||||
]
|
||||
);
|
||||
this.migrationStateModel._migrationDbs.forEach((db, index) => {
|
||||
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_NAME_FOR_DATABASE(db), this.migrationStateModel._targetDatabaseNames[index]));
|
||||
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_FILE_SHARE(db), this.migrationStateModel._databaseBackup.fileShares[index].name));
|
||||
});
|
||||
break;
|
||||
case NetworkContainerType.BLOB_CONTAINER:
|
||||
flexContainer.addItems(
|
||||
[
|
||||
createInformationRow(this._view, constants.TYPE, constants.BLOB_CONTAINER),
|
||||
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
|
||||
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.storageAccount.name),
|
||||
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.blob.storageAccount.location),
|
||||
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.blob.storageAccount.resourceGroup!),
|
||||
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.blob.storageAccount.name),
|
||||
createInformationRow(this._view, constants.BLOB_CONTAINER, this.migrationStateModel._databaseBackup.blob.blobContainer.name)
|
||||
]
|
||||
);
|
||||
this.migrationStateModel._migrationDbs.forEach((db, index) => {
|
||||
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_NAME_FOR_DATABASE(db), this.migrationStateModel._targetDatabaseNames[index]));
|
||||
flexContainer.addItem(createInformationRow(this._view, constants.TARGET_FILE_SHARE(db), this.migrationStateModel._databaseBackup.blobContainers[index].name));
|
||||
});
|
||||
}
|
||||
flexContainer.addItem(createHeadingTextComponent(this._view, constants.TARGET_NAME));
|
||||
this.migrationStateModel._migrationDbs.forEach((db, index) => {
|
||||
flexContainer.addItem(createInformationRow(this._view, db, this.migrationStateModel._targetDatabaseNames[index]));
|
||||
});
|
||||
return flexContainer;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user