diff --git a/extensions/sql-migration/images/background.svg b/extensions/sql-migration/images/background.svg
index 9bc057df35..02c9c49d34 100644
--- a/extensions/sql-migration/images/background.svg
+++ b/extensions/sql-migration/images/background.svg
@@ -16,21 +16,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/extensions/sql-migration/images/discard.svg b/extensions/sql-migration/images/discard.svg
new file mode 100644
index 0000000000..356c911744
--- /dev/null
+++ b/extensions/sql-migration/images/discard.svg
@@ -0,0 +1,3 @@
+
diff --git a/extensions/sql-migration/src/api/azure.ts b/extensions/sql-migration/src/api/azure.ts
index 09de559d90..94aeabeecf 100644
--- a/extensions/sql-migration/src/api/azure.ts
+++ b/extensions/sql-migration/src/api/azure.ts
@@ -220,6 +220,16 @@ export async function startMigrationCutover(account: azdata.Account, subscriptio
return response.response.data.value;
}
+export async function stopMigration(account: azdata.Account, subscription: Subscription, migrationStatus: DatabaseMigration): Promise {
+ const api = await getAzureCoreAPI();
+ const host = `https://eastus2euap.management.azure.com`;
+ const path = `${migrationStatus.id}/operations/${migrationStatus.properties.migrationOperationId}/cancel?api-version=2020-09-01-preview`;
+ const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host);
+ if (response.errors.length > 0) {
+ throw new Error(response.errors.toString());
+ }
+}
+
/**
* For now only east us euap is supported. Actual API calls will be added in the public release.
*/
diff --git a/extensions/sql-migration/src/constants/iconPathHelper.ts b/extensions/sql-migration/src/constants/iconPathHelper.ts
index 15e9de31f7..cebaba14f4 100644
--- a/extensions/sql-migration/src/constants/iconPathHelper.ts
+++ b/extensions/sql-migration/src/constants/iconPathHelper.ts
@@ -12,6 +12,7 @@ export interface IconPath {
export class IconPathHelper {
public static copy: IconPath;
+ public static discard: IconPath;
public static refresh: IconPath;
public static cutover: IconPath;
public static sqlMigrationLogo: IconPath;
@@ -27,6 +28,10 @@ export class IconPathHelper {
light: context.asAbsolutePath('images/copy.svg'),
dark: context.asAbsolutePath('images/copy.svg')
};
+ IconPathHelper.discard = {
+ light: context.asAbsolutePath('images/discard.svg'),
+ dark: context.asAbsolutePath('images/discard.svg')
+ };
IconPathHelper.refresh = {
light: context.asAbsolutePath('images/refresh.svg'),
dark: context.asAbsolutePath('images/refresh.svg')
diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts
index 12ecb9ce42..2bb94050b1 100644
--- a/extensions/sql-migration/src/constants/strings.ts
+++ b/extensions/sql-migration/src/constants/strings.ts
@@ -58,7 +58,7 @@ export const DATABASE_BACKUP_NETWORK_SHARE_LOCATION_LABEL = localize('sql.migrat
export const DATABASE_BACKUP_NETWORK_SHARE_WINDOWS_USER_LABEL = localize('sql.migration.network.share.windows.user.label', "Windows user account with read access to the network share location.");
export const DATABASE_BACKUP_NETWORK_SHARE_PASSWORD_LABEL = localize('sql.migration.network.share.password.label', "Password");
export const DATABASE_BACKUP_NETWORK_SHARE_PASSWORD_PLACEHOLDER = localize('sql.migration.network.share.password.placeholder', "Enter password");
-export const DATABASE_BACKUP_NETWORK_SHARE_AZURE_ACCOUNT_HELP = localize('sql.migration.network.share.azure.help', "Enter Azure storage account information where the backup will be copied");
+export const DATABASE_BACKUP_NETWORK_SHARE_AZURE_ACCOUNT_HELP = localize('sql.migration.network.share.azure.help', "Select the storage account where backup files will be copied to during migration");
export const DATABASE_BACKUP_NETWORK_SHARE_SUBSCRIPTION_LABEL = localize('sql.migration.network.share.subscription.label', "Select the subscription that contains the storage account.");
export const DATABASE_BACKUP_SUBSCRIPTION_PLACEHOLDER = localize('sql.migration.network.share.subscription.placeholder', "Select subscription");
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.");
@@ -87,7 +87,21 @@ 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);
+}
+export function TARGET_FILE_SHARE(dbName: string): string {
+ return localize('sql.migration.file.share', "Select the file share that contains the backup files for ‘{0}’", dbName);
+}
+export function TARGET_BLOB_CONTAINER(dbName: string): string {
+ return localize('sql.migration.blob.container', "Select the container that contains the backup files for ‘{0}’", dbName);
+}
+export const ENTER_NETWORK_SHARE_INFORMATION = localize('sql.migration.enter.network.share.information', "Enter network share path information for selected databases");
+export const ENTER_BLOB_CONTAINER_INFORMATION = localize('sql.migration.blob.container.information', "Enter the target name and select the blob container location for selected databases");
+export const ENTER_FILE_SHARE_INFORMATION = localize('sql.migration.enter.file.share.information', "Enter the target name and select the file share location of selected databases");
// integration runtime page
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Migration Controller");
@@ -168,8 +182,8 @@ export const SUMMARY_AZURE_STORAGE_SUBSCRIPTION = localize('sql.migration.summar
export const SUMMARY_AZURE_STORAGE = localize('sql.migration.summary.azure.storage', "Azure storage");
export const SUMMARY_IR_NODE = localize('sql.migration.ir.node', "Integration Runtime node");
export const NETWORK_SHARE = localize('sql.migration.network.share', "Network Share");
-export const BLOB_CONTAINER = localize('sql.migration.blob.container', "Blob Container");
-export const FILE_SHARE = localize('sql.migration.file.share', "File Share");
+export const BLOB_CONTAINER = localize('sql.migration.blob.container.title', "Blob Container");
+export const FILE_SHARE = localize('sql.migration.file.share.title', "File Share");
export const MIGRATION_STARTED = localize('sql.migration.started.notification', "Migration in progress");
// Open notebook quick pick string
@@ -184,8 +198,8 @@ export const DASHBOARD_DESCRIPTION = localize('sql.migration.dashboard.descripti
export const DASHBOARD_MIGRATE_TASK_BUTTON_TITLE = localize('sql.migration.dashboard.migrate.task.button', "Migrate to Azure SQL");
export const DASHBOARD_MIGRATE_TASK_BUTTON_DESCRIPTION = localize('sql.migration.dashboard.migrate.task.button.description', "Migrate SQL Server instance to Azure SQL.");
export const DATABASE_MIGRATION_STATUS = localize('sql.migration.database.migration.status', "Database Migration Status");
-export const HELP_VIDEO1_TITLE = localize('sql.migration.dashboard.video1.title', "Migrate to SQL Server to SQL Managed Instance");
-export const HELP_VIDEO2_TITLE = localize('sql.migration.dashboard.video2.title', "Migrate to SQL Server to SQL Virtual Machine");
+export const HELP_VIDEO1_TITLE = localize('sql.migration.dashboard.video1.title', "Migrate SQL Server to SQL Managed Instance");
+export const HELP_VIDEO2_TITLE = localize('sql.migration.dashboard.video2.title', "Migrate SQL Server to SQL Virtual Machine");
export const HELP_LINK1_TITLE = localize('sql.migration.dashboard.link1.title', "Migrating your SQL Server to cloud");
export const HELP_LINK1_DESCRIPTION = localize('sql.migration.dashboard.link1.description', "Lorem ipsum dolor sit amet, consectetur adipi. Lorem ipsum dolor sit amet, consectetur adipi. Lorem ipsum.");
export const HELP_TITLE = localize('sql.migration.dashboard.help.title', "Help Articles and Video Links");
@@ -212,11 +226,11 @@ export const SOURCE_VERSION = localize('sql.migration.source.version', "Source v
export const TARGET_SERVER = localize('sql.migration.target.server', "Target server");
export const TARGET_VERSION = localize('sql.migration.target.version', "Target version");
export const MIGRATION_STATUS = localize('sql.migration.migration.status', "Migration status");
-export const FULL_BACKUP_FILES = localize('sql.migration.full.backup.files', "Full backup files(s)");
+export const FULL_BACKUP_FILES = localize('sql.migration.full.backup.files', "Full backup files");
export const LAST_APPLIED_LSN = localize('sql.migration.last.applied.lsn', "Last applied LSN");
-export const LAST_APPLIED_BACKUP_FILES = localize('sql.migration.last.applied.backup.files', "Last applied backup file(s)");
-export const LAST_APPLIED_BACKUP_FILES_TAKEN_ON = localize('sql.migration.last.applied.files.taken.on', "Last applied backup file(s) taken on");
-export const ACTIVE_BACKUP_FILES = localize('sql.migration.active.backup.files', "Active Backup file(s)");
+export const LAST_APPLIED_BACKUP_FILES = localize('sql.migration.last.applied.backup.files', "Last applied backup files");
+export const LAST_APPLIED_BACKUP_FILES_TAKEN_ON = localize('sql.migration.last.applied.files.taken.on', "Last applied backup files taken on");
+export const ACTIVE_BACKUP_FILES = localize('sql.migration.active.backup.files', "Active Backup files");
export const STATUS = localize('sql.migration.status', "Status");
export const BACKUP_START_TIME = localize('sql.migration.backup.start.time', "Backup start time");
export const FIRST_LSN = localize('sql.migration.first.lsn', "First LSN");
@@ -224,7 +238,7 @@ export const LAST_LSN = localize('sql.migration.last.LSN', "Last LSN");
export const CANNOT_START_CUTOVER_ERROR = localize('sql.migration.cannot.start.cutover.error', "Cannot start the cutover process until all the migrations are done. Click refresh to fetch the latest file status");
export const AZURE_SQL_DATABASE_MANAGED_INSTANCE = localize('sql.migration.azure.sql.database.managed.instance', "Azure SQL Database Managed Instance");
export const AZURE_SQL_DATABASE_VIRTUAL_MACHINE = localize('sql.migration.azure.sql.database.virtual.machine', "Azure SQL Database Virtual Machine");
-
+export const CANCEL_MIGRATION = localize('sql.migration.cancel.migration', "Cancel migration");
export function ACTIVE_BACKUP_FILES_ITEMS(fileCount: number) {
if (fileCount === 1) {
return localize('sql.migration.active.backup.files.items', "Active Backup files (1 item)");
diff --git a/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts
index dd0c5c010f..026108ec1f 100644
--- a/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts
+++ b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts
@@ -184,6 +184,9 @@ export class DashboardWidget {
label: taskMetaData.title,
title: taskMetaData.title,
width: maxWidth,
+ CSSStyles: {
+ 'border': '1px solid'
+ }
}).component();
buttonContainer.onDidClick(async () => {
if (taskMetaData.command) {
@@ -267,12 +270,17 @@ export class DashboardWidget {
const localMigrations = MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection);
for (let i = 0; i < localMigrations.length; i++) {
const localMigration = localMigrations[i];
- localMigration.migrationContext = await getDatabaseMigration(
- localMigration.azureAccount,
- localMigration.subscription,
- localMigration.targetManagedInstance.location,
- localMigration.migrationContext.id
- );
+ try {
+ localMigration.migrationContext = await getDatabaseMigration(
+ localMigration.azureAccount,
+ localMigration.subscription,
+ localMigration.targetManagedInstance.location,
+ localMigration.migrationContext.id
+ );
+ } catch (e) {
+ console.log(e);
+ }
+
localMigration.sourceConnectionProfile = currentConnection;
}
return localMigrations;
@@ -329,7 +337,7 @@ export class DashboardWidget {
'width': '400px',
'height': '50px',
'margin-top': '10px',
- 'box-shadow': '0 1px 2px 0 rgba(0,0,0,0.2)'
+ 'border': '1px solid'
}
}).component();
@@ -389,7 +397,7 @@ export class DashboardWidget {
justifyContent: 'flex-start',
}).withProps({
CSSStyles: {
- 'border': '1px solid rgba(0, 0, 0, 0.1)',
+ 'border': '1px solid',
'padding': '15px'
}
}).component();
@@ -436,7 +444,7 @@ export class DashboardWidget {
buttonContainer.addItem(viewAllButton, {
flex: 'auto',
CSSStyles: {
- 'border-right': '1px solid rgba(0, 0, 0, 0.7)',
+ 'border-right': '1px solid ',
'width': '40px',
}
});
@@ -492,7 +500,7 @@ export class DashboardWidget {
justifyContent: 'flex-start',
}).withProps({
CSSStyles: {
- 'border': '1px solid rgba(0, 0, 0, 0.1)',
+ 'border': '1px solid',
'padding': '15px'
}
}).component();
@@ -518,7 +526,7 @@ export class DashboardWidget {
const links = [{
title: loc.HELP_LINK1_TITLE,
description: loc.HELP_LINK1_DESCRIPTION,
- link: 'www.microsoft.com' //TODO: add proper link over here.
+ link: 'https://www.microsoft.com' //TODO: add proper link over here.
}];
const styles = {
diff --git a/extensions/sql-migration/src/dialog/assessmentResults/assessmentResultsDialog.ts b/extensions/sql-migration/src/dialog/assessmentResults/assessmentResultsDialog.ts
index bcd2384adc..fbb5344e51 100644
--- a/extensions/sql-migration/src/dialog/assessmentResults/assessmentResultsDialog.ts
+++ b/extensions/sql-migration/src/dialog/assessmentResults/assessmentResultsDialog.ts
@@ -128,6 +128,7 @@ export class AssessmentResultsDialog {
protected async execute() {
this.model._migrationDbs = this._tree.selectedDbs();
this.skuRecommendationPage.refreshDatabaseCount(this._model._migrationDbs.length);
+ this.model.refreshDatabaseBackupPage = true;
this._isOpen = false;
}
diff --git a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts
index 8346a3e093..e11b9b568e 100644
--- a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts
+++ b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts
@@ -16,6 +16,7 @@ export class MigrationCutoverDialog {
private _databaseTitleName!: azdata.TextComponent;
private _databaseCutoverButton!: azdata.ButtonComponent;
private _refresh!: azdata.ButtonComponent;
+ private _cancel!: azdata.ButtonComponent;
private _serverName!: azdata.TextComponent;
private _serverVersion!: azdata.TextComponent;
@@ -290,6 +291,27 @@ export class MigrationCutoverDialog {
}
});
+ this._cancel = this._view.modelBuilder.button().withProps({
+ iconPath: IconPathHelper.discard,
+ iconHeight: '16px',
+ iconWidth: '16px',
+ label: loc.CANCEL_MIGRATION,
+ height: '55px',
+ width: '130px'
+ }).component();
+
+ this._cancel.onDidClick((e) => {
+ this.cancelMigration();
+ });
+
+ header.addItem(this._cancel, {
+ flex: '0',
+ CSSStyles: {
+ 'width': '130px'
+ }
+ });
+
+
this._refresh = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.refresh,
iconHeight: '16px',
@@ -392,6 +414,7 @@ export class MigrationCutoverDialog {
this._databaseCutoverButton.enabled = true;
} else {
this._databaseCutoverButton.enabled = false;
+ this._cancel.enabled = false;
}
} catch (e) {
console.log(e);
@@ -419,7 +442,10 @@ export class MigrationCutoverDialog {
value: value,
CSSStyles: {
'margin-top': '5px',
- 'margin-bottom': '0'
+ 'margin-bottom': '0',
+ 'width': '100%',
+ 'overflow': 'hidden',
+ 'text-overflow': 'ellipses'
}
}).component();
flexContainer.addItem(textComponent);
@@ -428,6 +454,11 @@ export class MigrationCutoverDialog {
text: textComponent
};
}
+
+ private async cancelMigration(): Promise {
+ await this._model.cancelMigration();
+ await this.refreshStatus();
+ }
}
interface ActiveBackupFileSchema {
diff --git a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts
index 66d5a82dee..5a7f7b74c7 100644
--- a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts
+++ b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { getMigrationStatus, DatabaseMigration, startMigrationCutover } from '../../api/azure';
+import { getMigrationStatus, DatabaseMigration, startMigrationCutover, stopMigration } from '../../api/azure';
import { MigrationContext } from '../../models/migrationLocalStorage';
@@ -36,4 +36,19 @@ export class MigrationCutoverDialogModel {
}
return undefined!;
}
+
+ public async cancelMigration(): Promise {
+ try {
+ if (this.migrationStatus) {
+ await stopMigration(
+ this._migration.azureAccount,
+ this._migration.subscription,
+ this.migrationStatus
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ return undefined!;
+ }
}
diff --git a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts
index 34d014bdb9..d66df04a73 100644
--- a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts
+++ b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts
@@ -10,6 +10,7 @@ import { MigrationContext } from '../../models/migrationLocalStorage';
import { MigrationCutoverDialog } from '../migrationCutover/migrationCutoverDialog';
import { MigrationCategory, MigrationStatusDialogModel } from './migrationStatusDialogModel';
import * as loc from '../../constants/strings';
+import { getDatabaseMigration } from '../../api/azure';
export class MigrationStatusDialog {
private _model: MigrationStatusDialogModel;
private _dialogObject!: azdata.window.Dialog;
@@ -84,6 +85,10 @@ export class MigrationStatusDialog {
label: 'Refresh',
}).component();
+ this._refresh.onDidClick((e) => {
+ this.refreshTable();
+ });
+
const flexContainer = this._view.modelBuilder.flexContainer().component();
flexContainer.addItem(this._searchBox, {
@@ -136,7 +141,7 @@ export class MigrationStatusDialog {
height: '20px'
}).component();
const sqlMigrationName = this._view.modelBuilder.hyperlink().withProps({
- label: migration.migrationContext.name,
+ label: migration.targetManagedInstance.name,
url: ''
}).component();
sqlMigrationName.onDidClick((e) => {
@@ -184,6 +189,19 @@ export class MigrationStatusDialog {
}
}
+ private refreshTable(): void {
+ this._model._migrations.forEach(async (migration) => {
+ migration.migrationContext = await getDatabaseMigration(
+ migration.azureAccount,
+ migration.subscription,
+ migration.targetManagedInstance.location,
+ migration.migrationContext.id
+ );
+ });
+
+ this.populateMigrationTable();
+ }
+
private createStatusTable(): azdata.DeclarativeTableComponent {
this._statusTable = this._view.modelBuilder.declarativeTable().withProps({
columns: [
diff --git a/extensions/sql-migration/src/models/stateMachine.ts b/extensions/sql-migration/src/models/stateMachine.ts
index e8dcaf8d11..e18c0817d6 100644
--- a/extensions/sql-migration/src/models/stateMachine.ts
+++ b/extensions/sql-migration/src/models/stateMachine.ts
@@ -52,15 +52,15 @@ export interface NetworkShare {
export interface DatabaseBackupModel {
migrationCutover: MigrationCutover;
networkContainerType: NetworkContainerType;
- networkShareLocation: string;
+ networkShareLocations: string[];
windowsUser: string;
password: string;
subscription: azureResource.AzureResourceSubscription;
storageAccount: StorageAccount;
storageKey: string;
azureSecurityToken: string;
- fileShare: azureResource.FileShare;
- blobContainer: azureResource.BlobContainer;
+ fileShares: azureResource.FileShare[];
+ blobContainers: azureResource.BlobContainer[];
}
export interface Model {
readonly sourceConnectionId: string;
@@ -91,6 +91,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _storageAccounts!: StorageAccount[];
public _fileShares!: azureResource.FileShare[];
public _blobContainers!: azureResource.BlobContainer[];
+ public _refreshNetworkShareLocation!: azureResource.BlobContainer[];
+ public _targetDatabaseNames!: string[];
public _migrationController!: SqlMigrationController;
public _migrationControllers!: SqlMigrationController[];
@@ -102,7 +104,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
private _skuRecommendations: SKURecommendations | undefined;
private _assessmentResults: mssql.SqlMigrationAssessmentResultItem[] | undefined;
-
+ public refreshDatabaseBackupPage!: boolean;
constructor(
private readonly _extensionContext: vscode.ExtensionContext,
@@ -467,7 +469,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
},
SourceLocation: {
FileShare: {
- Path: this._databaseBackup.networkShareLocation,
+ Path: '',
Username: this._databaseBackup.windowsUser,
Password: this._databaseBackup.password,
}
@@ -482,19 +484,19 @@ export class MigrationStateModel implements Model, vscode.Disposable {
}
};
- this._migrationDbs.forEach(async (db) => {
+ this._migrationDbs.forEach(async (db, index) => {
requestBody.properties.SourceDatabaseName = db;
try {
+ requestBody.properties.BackupConfiguration.SourceLocation.FileShare.Path = this._databaseBackup.networkShareLocations[index];
const response = await startDatabaseMigration(
this._azureAccount,
this._targetSubscription,
this._migrationController?.properties.location!,
this._targetServerInstance,
- db,
+ this._targetDatabaseNames[index],
requestBody
);
-
if (response.status === 201) {
MigrationLocalStorage.saveMigration(
currentConnection!,
diff --git a/extensions/sql-migration/src/wizard/databaseBackupPage.ts b/extensions/sql-migration/src/wizard/databaseBackupPage.ts
index 46f8cdac32..f06850d281 100644
--- a/extensions/sql-migration/src/wizard/databaseBackupPage.ts
+++ b/extensions/sql-migration/src/wizard/databaseBackupPage.ts
@@ -7,27 +7,31 @@ import * as azdata from 'azdata';
import { EOL } from 'os';
import { getStorageAccountAccessKeys } from '../api/azure';
import { MigrationWizardPage } from '../models/migrationWizardPage';
-import { MigrationCutover, MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
+import { MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import * as vscode from 'vscode';
export class DatabaseBackupPage extends MigrationWizardPage {
+ private _view!: azdata.ModelView;
private _networkShareContainer!: azdata.FlexContainer;
private _networkShareContainerSubscriptionDropdown!: azdata.DropDownComponent;
private _networkShareContainerStorageAccountDropdown!: azdata.DropDownComponent;
- private _networkShareLocationText!: azdata.InputBoxComponent;
private _windowsUserAccountText!: azdata.InputBoxComponent;
private _passwordText!: azdata.InputBoxComponent;
+ private _networkShareDatabaseConfigContainer!: azdata.FlexContainer;
+ private _networkShareLocations!: azdata.InputBoxComponent[];
private _blobContainer!: azdata.FlexContainer;
private _blobContainerSubscriptionDropdown!: azdata.DropDownComponent;
private _blobContainerStorageAccountDropdown!: azdata.DropDownComponent;
- private _blobContainerBlobDropdown!: azdata.DropDownComponent;
+ private _blobContainerDatabaseConfigContainer!: azdata.FlexContainer;
+ private _blobContainerDropdowns!: azdata.DropDownComponent[];
private _fileShareContainer!: azdata.FlexContainer;
private _fileShareSubscriptionDropdown!: azdata.DropDownComponent;
private _fileShareStorageAccountDropdown!: azdata.DropDownComponent;
- private _fileShareFileShareDropdown!: azdata.DropDownComponent;
+ private _fileShareDatabaseConfigContainer!: azdata.FlexContainer;
+ private _fileShareDropdowns!: azdata.DropDownComponent[];
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.DATABASE_BACKUP_PAGE_TITLE), migrationStateModel);
@@ -35,7 +39,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
protected async registerContent(view: azdata.ModelView): Promise {
-
+ this._view = view;
this._networkShareContainer = this.createNetworkShareContainer(view);
this._blobContainer = this.createBlobContainer(view);
this._fileShareContainer = this.createFileShareContainer(view);
@@ -56,7 +60,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
title: '',
component: networkContainer
},
- this.migrationModeContainer(view),
]
);
await view.initializeModel(form.component());
@@ -68,7 +71,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
const networkShareButton = view.modelBuilder.radioButton()
.withProps({
name: buttonGroup,
- label: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL
+ label: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL,
+ checked: true
}).component();
networkShareButton.onDidChangeCheckedState((e) => {
@@ -150,26 +154,19 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._fileShareStorageAccountDropdown.onValueChanged(async (value) => {
if (value.selected) {
this.migrationStateModel._databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index);
- this.migrationStateModel._databaseBackup.fileShare = undefined!;
+ this.migrationStateModel._databaseBackup.fileShares = undefined!;
await this.loadFileShareDropdown();
}
});
- const fileShareLabel = view.modelBuilder.text()
- .withProps({
- value: constants.DATABASE_BACKUP_FILE_SHARE_LABEL,
- requiredIndicator: true,
- }).component();
- this._fileShareFileShareDropdown = view.modelBuilder.dropDown()
- .withProps({
- required: true
- }).component();
- this._fileShareFileShareDropdown.onValueChanged((value) => {
- if (value.selected) {
- this.migrationStateModel._databaseBackup.fileShare = this.migrationStateModel.getFileShare(value.index);
- }
- });
+ const fileShareDatabaseConfigHeader = view.modelBuilder.text().withProps({
+ value: constants.ENTER_FILE_SHARE_INFORMATION
+ }).component();
+
+ this._fileShareDatabaseConfigContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column'
+ }).component();
const flexContainer = view.modelBuilder.flexContainer()
.withItems(
@@ -178,8 +175,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._fileShareSubscriptionDropdown,
storageAccountLabel,
this._fileShareStorageAccountDropdown,
- fileShareLabel,
- this._fileShareFileShareDropdown
+ fileShareDatabaseConfigHeader,
+ this._fileShareDatabaseConfigContainer
]
).withLayout({
flexFlow: 'column'
@@ -220,24 +217,19 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._blobContainerStorageAccountDropdown.onValueChanged(async (value) => {
if (value.selected) {
this.migrationStateModel._databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index);
- this.migrationStateModel._databaseBackup.blobContainer = undefined!;
+ this.migrationStateModel._databaseBackup.blobContainers = undefined!;
await this.loadBlobContainerDropdown();
}
});
- const containerLabel = view.modelBuilder.text().withProps({
- value: constants.DATABASE_BACKUP_BLOB_STORAGE_ACCOUNT_CONTAINER_LABEL,
- requiredIndicator: true,
+
+ const blobContainerDatabaseConfigHeader = view.modelBuilder.text().withProps({
+ value: constants.ENTER_BLOB_CONTAINER_INFORMATION
+ }).component();
+
+ this._blobContainerDatabaseConfigContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column'
}).component();
- this._blobContainerBlobDropdown = view.modelBuilder.dropDown()
- .withProps({
- required: true
- }).component();
- this._blobContainerBlobDropdown.onValueChanged((value) => {
- if (value.selected) {
- this.migrationStateModel._databaseBackup.blobContainer = this.migrationStateModel.getBlobContainer(value.index);
- }
- });
const flexContainer = view.modelBuilder.flexContainer()
.withItems(
@@ -246,8 +238,8 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._blobContainerSubscriptionDropdown,
storageAccountLabel,
this._blobContainerStorageAccountDropdown,
- containerLabel,
- this._blobContainerBlobDropdown
+ blobContainerDatabaseConfigHeader,
+ this._blobContainerDatabaseConfigContainer
]
).withLayout({
flexFlow: 'column'
@@ -264,31 +256,6 @@ export class DatabaseBackupPage extends MigrationWizardPage {
value: constants.DATABASE_BACKUP_NC_NETWORK_SHARE_HELP_TEXT,
}).component();
- const networkShareLocationLabel = view.modelBuilder.text()
- .withProps({
- value: constants.DATABASE_BACKUP_NETWORK_SHARE_LOCATION_LABEL,
- requiredIndicator: true,
- }).component();
- this._networkShareLocationText = view.modelBuilder.inputBox()
- .withProps({
- placeHolder: '\\\\Servername.domainname.com\\Backupfolder',
- required: true,
- validationErrorMessage: constants.INVALID_NETWORK_SHARE_LOCATION
- })
- .withValidation((component) => {
- if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
- if (component.value) {
- if (!/(?<=\\\\)[^\\]*/.test(component.value)) {
- return false;
- }
- }
- }
- return true;
- }).component();
- this._networkShareLocationText.onTextChanged((value) => {
- this.migrationStateModel._databaseBackup.networkShareLocation = value;
- });
-
const windowsUserAccountLabel = view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_NETWORK_SHARE_WINDOWS_USER_LABEL,
@@ -366,20 +333,30 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}
});
+
+ const networkShareDatabaseConfigHeader = view.modelBuilder.text().withProps({
+ value: constants.ENTER_NETWORK_SHARE_INFORMATION
+ }).component();
+
+ this._networkShareDatabaseConfigContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column'
+ }).component();
+
+
const flexContainer = view.modelBuilder.flexContainer().withItems(
[
+ azureAccountHelpText,
networkShareHelpText,
- networkShareLocationLabel,
- this._networkShareLocationText,
+ subscriptionLabel,
+ this._networkShareContainerSubscriptionDropdown,
+ storageAccountLabel,
+ this._networkShareContainerStorageAccountDropdown,
windowsUserAccountLabel,
this._windowsUserAccountText,
passwordLabel,
this._passwordText,
- azureAccountHelpText,
- subscriptionLabel,
- this._networkShareContainerSubscriptionDropdown,
- storageAccountLabel,
- this._networkShareContainerStorageAccountDropdown
+ networkShareDatabaseConfigHeader,
+ this._networkShareDatabaseConfigContainer
]
).withLayout({
flexFlow: 'column'
@@ -390,57 +367,130 @@ export class DatabaseBackupPage extends MigrationWizardPage {
return flexContainer;
}
- private migrationModeContainer(view: azdata.ModelView): azdata.FormComponent {
- const description = view.modelBuilder.text().withProps({
- value: constants.DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION
- }).component();
-
- const buttonGroup = 'cutoverContainer';
-
- const onlineButton = view.modelBuilder.radioButton().withProps({
- label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
- name: buttonGroup,
- checked: true
- }).component();
-
- this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.ONLINE;
-
- onlineButton.onDidChangeCheckedState((e) => {
- if (e) {
- this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.ONLINE;
- }
- });
-
- const offlineButton = view.modelBuilder.radioButton().withProps({
- label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
- name: buttonGroup
- }).component();
-
- offlineButton.onDidChangeCheckedState((e) => {
- if (e) {
- vscode.window.showInformationMessage('Feature coming soon');
- onlineButton.checked = true;
- //this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.OFFLINE;
- }
- });
-
- const flexContainer = view.modelBuilder.flexContainer().withItems(
- [
- description,
- onlineButton,
- offlineButton
- ]
- ).withLayout({
- flexFlow: 'column'
- }).component();
-
- return {
- title: constants.DATABASE_BACKUP_MIGRATION_MODE_LABEL,
- component: flexContainer
- };
- }
-
public async onPageEnter(): Promise {
+ if (this.migrationStateModel.refreshDatabaseBackupPage) {
+
+ this._networkShareLocations = [];
+ this._fileShareDropdowns = [];
+ this._blobContainerDropdowns = [];
+ this.migrationStateModel._targetDatabaseNames = [];
+ this.migrationStateModel._databaseBackup.networkShareLocations = [];
+ this.migrationStateModel._databaseBackup.fileShares = [];
+ this.migrationStateModel._databaseBackup.blobContainers = [];
+ this._networkShareDatabaseConfigContainer.clearItems();
+ this._fileShareDatabaseConfigContainer.clearItems();
+ this._blobContainerDatabaseConfigContainer.clearItems();
+
+ this.migrationStateModel._migrationDbs.forEach((db, index) => {
+ this.migrationStateModel._targetDatabaseNames.push('');
+ const targetNameLabel = constants.TARGET_NAME_FOR_DATABASE(db);
+ const targetNameNetworkInputBoxLabel = this._view.modelBuilder.text().withProps({
+ value: targetNameLabel,
+ requiredIndicator: true
+ }).component();
+ const targetNameNetworkInputBox = this._view.modelBuilder.inputBox().withProps({
+ required: true
+ }).component();
+ targetNameNetworkInputBox.onTextChanged((value) => {
+ this.migrationStateModel._targetDatabaseNames[index] = value;
+ });
+
+ const networkLocationInputBoxLabel = this._view.modelBuilder.text().withProps({
+ value: constants.TARGET_NETWORK_SHARE_LOCATION(db),
+ requiredIndicator: true
+ }).component();
+ const networkLocationInputBox = this._view.modelBuilder.inputBox().withProps({
+ placeHolder: '\\\\Servername.domainname.com\\Backupfolder',
+ required: true,
+ validationErrorMessage: constants.INVALID_NETWORK_SHARE_LOCATION
+ }).withValidation((component) => {
+ if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
+ if (component.value) {
+ if (!/(?<=\\\\)[^\\]*/.test(component.value)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }).component();
+ networkLocationInputBox.onTextChanged((value) => {
+ this.validateFields();
+ this.migrationStateModel._databaseBackup.networkShareLocations[index] = value;
+ });
+ this.migrationStateModel._databaseBackup.networkShareLocations.push(undefined!);
+ this._networkShareLocations.push(networkLocationInputBox);
+ this._networkShareDatabaseConfigContainer.addItems(
+ [
+ targetNameNetworkInputBoxLabel,
+ targetNameNetworkInputBox,
+ networkLocationInputBoxLabel,
+ networkLocationInputBox
+ ]
+ );
+
+ const targetNameFileInputBoxLabel = this._view.modelBuilder.text().withProps({
+ value: targetNameLabel
+ }).component();
+ const targetNameFileInputBox = this._view.modelBuilder.inputBox().withProps({
+ }).component();
+ const fileShareLabel = this._view.modelBuilder.text()
+ .withProps({
+ value: constants.TARGET_FILE_SHARE(db),
+ requiredIndicator: true,
+ }).component();
+ const fileShareDropdown = this._view.modelBuilder.dropDown()
+ .withProps({
+ }).component();
+ fileShareDropdown.onValueChanged((value) => {
+ if (value.selected) {
+ this.validateFields();
+ this.migrationStateModel._databaseBackup.fileShares[index] = this.migrationStateModel.getFileShare(value.index);
+ }
+ });
+ this.migrationStateModel._databaseBackup.fileShares.push(undefined!);
+ this._fileShareDropdowns.push(fileShareDropdown);
+ this._fileShareDatabaseConfigContainer.addItems(
+ [
+ targetNameFileInputBoxLabel,
+ targetNameFileInputBox,
+ fileShareLabel,
+ fileShareDropdown
+ ]
+ );
+
+ const targetNameBlobInputBoxLabel = this._view.modelBuilder.text().withProps({
+ value: targetNameLabel
+ }).component();
+ const targetNameBlobInputBox = this._view.modelBuilder.inputBox().withProps({
+ }).component();
+ const blobContainerLabel = this._view.modelBuilder.text()
+ .withProps({
+ value: constants.TARGET_BLOB_CONTAINER(db),
+ requiredIndicator: true,
+ }).component();
+ const blobContainerDropdown = this._view.modelBuilder.dropDown()
+ .withProps({
+ }).component();
+ blobContainerDropdown.onValueChanged((value) => {
+ if (value.selected) {
+ this.validateFields();
+ this.migrationStateModel._databaseBackup.blobContainers[index] = this.migrationStateModel.getBlobContainer(value.index);
+ }
+ });
+ this.migrationStateModel._databaseBackup.fileShares.push(undefined!);
+ this._blobContainerDropdowns.push(blobContainerDropdown);
+ this._blobContainerDatabaseConfigContainer.addItems(
+ [
+ targetNameBlobInputBoxLabel,
+ targetNameBlobInputBox,
+ blobContainerLabel,
+ blobContainerDropdown
+ ]
+ );
+ });
+
+ this.migrationStateModel.refreshDatabaseBackupPage = false;
+ }
await this.getSubscriptionValues();
this.wizard.registerNavigationValidator((pageChangeInfo) => {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
@@ -465,8 +515,11 @@ export class DatabaseBackupPage extends MigrationWizardPage {
if ((this._blobContainerStorageAccountDropdown.value).displayName === constants.NO_STORAGE_ACCOUNT_FOUND) {
errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
}
- if ((this._blobContainerBlobDropdown.value).displayName === constants.NO_BLOBCONTAINERS_FOUND) {
- errors.push(constants.INVALID_BLOBCONTAINER_ERROR);
+ for (let i = 0; i < this._blobContainerDropdowns.length; i++) {
+ if ((this._blobContainerDropdowns[i].value).displayName === constants.NO_BLOBCONTAINERS_FOUND) {
+ errors.push(constants.INVALID_BLOBCONTAINER_ERROR);
+ break;
+ }
}
break;
case NetworkContainerType.FILE_SHARE:
@@ -476,8 +529,11 @@ export class DatabaseBackupPage extends MigrationWizardPage {
if ((this._fileShareStorageAccountDropdown.value).displayName === constants.NO_STORAGE_ACCOUNT_FOUND) {
errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
}
- if ((this._fileShareFileShareDropdown.value).displayName === constants.NO_FILESHARES_FOUND) {
- errors.push(constants.INVALID_FILESHARE_ERROR);
+ for (let i = 0; i < this._fileShareDropdowns.length; i++) {
+ if ((this._fileShareDropdowns[i].value).displayName === constants.NO_FILESHARES_FOUND) {
+ errors.push(constants.INVALID_FILESHARE_ERROR);
+ break;
+ }
}
break;
}
@@ -508,8 +564,10 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._fileShareContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.FILE_SHARE) ? 'inline' : 'none' });
this._blobContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none' });
this._networkShareContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none' });
- this._networkShareLocationText.updateProperties({
- required: containerType === NetworkContainerType.NETWORK_SHARE
+ this._networkShareLocations.forEach((inputBox) => {
+ inputBox.updateProperties({
+ required: containerType === NetworkContainerType.NETWORK_SHARE
+ });
});
this._windowsUserAccountText.updateProperties({
required: containerType === NetworkContainerType.NETWORK_SHARE
@@ -517,21 +575,47 @@ export class DatabaseBackupPage extends MigrationWizardPage {
this._passwordText.updateProperties({
required: containerType === NetworkContainerType.NETWORK_SHARE
});
-
- this._networkShareLocationText.validate();
+ this._networkShareLocations.forEach((inputBox) => {
+ inputBox.validate();
+ });
this._windowsUserAccountText.validate();
this._passwordText.validate();
this._networkShareContainerSubscriptionDropdown.validate();
this._networkShareContainerStorageAccountDropdown.validate();
this._blobContainerSubscriptionDropdown.validate();
this._blobContainerStorageAccountDropdown.validate();
- this._blobContainerBlobDropdown.validate();
+ this._blobContainerDropdowns.forEach((dropdown) => {
+ dropdown.validate();
+ });
this._fileShareSubscriptionDropdown.validate();
this._fileShareStorageAccountDropdown.validate();
- this._fileShareFileShareDropdown.validate();
+ this._fileShareDropdowns.forEach(dropdown => {
+ dropdown.validate();
+ });
}
+
+ private validateFields(): void {
+ this._networkShareLocations.forEach((inputBox) => {
+ inputBox.validate();
+ });
+ this._windowsUserAccountText.validate();
+ this._passwordText.validate();
+ this._networkShareContainerSubscriptionDropdown.validate();
+ this._networkShareContainerStorageAccountDropdown.validate();
+ this._blobContainerSubscriptionDropdown.validate();
+ this._blobContainerStorageAccountDropdown.validate();
+ this._blobContainerDropdowns.forEach((dropdown) => {
+ dropdown.validate();
+ });
+ this._fileShareSubscriptionDropdown.validate();
+ this._fileShareStorageAccountDropdown.validate();
+ this._fileShareDropdowns.forEach((dropdown) => {
+ dropdown.validate();
+ });
+ }
+
private async getSubscriptionValues(): Promise {
if (!this.migrationStateModel._databaseBackup.subscription) {
this._networkShareContainerSubscriptionDropdown.loading = true;
@@ -568,14 +652,18 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async loadFileShareStorageDropdown(): Promise {
if (!this.migrationStateModel._databaseBackup.storageAccount) {
this._fileShareStorageAccountDropdown.loading = true;
- this._fileShareFileShareDropdown.loading = true;
+ this._fileShareDropdowns.forEach((dropdown) => {
+ dropdown.loading = true;
+ });
try {
this._fileShareStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription);
} catch (error) {
console.log(error);
} finally {
this._fileShareStorageAccountDropdown.loading = false;
- this._fileShareFileShareDropdown.loading = false;
+ this._fileShareDropdowns.forEach((dropdown) => {
+ dropdown.loading = false;
+ });
}
}
}
@@ -583,41 +671,58 @@ export class DatabaseBackupPage extends MigrationWizardPage {
private async loadblobStorageDropdown(): Promise {
if (!this.migrationStateModel._databaseBackup.storageAccount) {
this._blobContainerStorageAccountDropdown.loading = true;
- this._blobContainerBlobDropdown.loading = true;
+ this._blobContainerDropdowns.forEach((dropdown) => {
+ dropdown.loading = true;
+ });
try {
this._blobContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription);
} catch (error) {
console.log(error);
} finally {
this._blobContainerStorageAccountDropdown.loading = false;
- this._blobContainerBlobDropdown.loading = true;
-
+ this._blobContainerDropdowns.forEach((dropdown) => {
+ dropdown.loading = false;
+ });
}
}
}
private async loadFileShareDropdown(): Promise {
if (!this.migrationStateModel._fileShares) {
- this._fileShareFileShareDropdown.loading = true;
+ this._fileShareDropdowns.forEach((dropdown) => {
+ dropdown.loading = true;
+ });
try {
- this._fileShareFileShareDropdown.values = await this.migrationStateModel.getFileShareValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.storageAccount);
+ const fileShareValues = await this.migrationStateModel.getFileShareValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.storageAccount);
+ this._fileShareDropdowns.forEach((dropdown) => {
+ dropdown.values = fileShareValues;
+ });
} catch (error) {
console.log(error);
} finally {
- this._fileShareFileShareDropdown.loading = false;
+ this._fileShareDropdowns.forEach((dropdown) => {
+ dropdown.loading = true;
+ });
}
}
}
private async loadBlobContainerDropdown(): Promise {
if (!this.migrationStateModel._blobContainers) {
- this._blobContainerBlobDropdown.loading = true;
+ this._blobContainerDropdowns.forEach((dropdown) => {
+ dropdown.loading = true;
+ });
try {
- this._blobContainerBlobDropdown.values = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.storageAccount);
+ const blobContainerValues = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.storageAccount);
+ this._blobContainerDropdowns.forEach((dropdown) => {
+ dropdown.values = blobContainerValues;
+ });
} catch (error) {
console.log(error);
} finally {
- this._blobContainerBlobDropdown.loading = false;
+ this._blobContainerDropdowns.forEach((dropdown) => {
+ dropdown.loading = false;
+ });
}
}
}
diff --git a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts
index 59ab2bc86e..b0d7ea3e81 100644
--- a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts
+++ b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts
@@ -132,7 +132,9 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
text: ''
};
this.migrationStateModel._migrationController = this.migrationStateModel.getMigrationController(value.index);
- await this.loadControllerStatus();
+ if (value !== constants.MIGRATION_CONTROLLER_NOT_FOUND_ERROR) {
+ await this.loadControllerStatus();
+ }
}
});
@@ -169,7 +171,6 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
private async loadControllerStatus(): Promise {
this._statusLoadingComponent.loading = true;
-
try {
this._migrationDetailsContainer.clearItems();
diff --git a/extensions/sql-migration/src/wizard/migrationModePage.ts b/extensions/sql-migration/src/wizard/migrationModePage.ts
new file mode 100644
index 0000000000..0ed8a2f913
--- /dev/null
+++ b/extensions/sql-migration/src/wizard/migrationModePage.ts
@@ -0,0 +1,78 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as azdata from 'azdata';
+import * as vscode from 'vscode';
+import { MigrationWizardPage } from '../models/migrationWizardPage';
+import { MigrationCutover, MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
+import * as constants from '../constants/strings';
+
+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.wizardPage.description = constants.DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION;
+ }
+
+ protected async registerContent(view: azdata.ModelView): Promise {
+ const form = view.modelBuilder.formContainer()
+ .withFormItems(
+ [
+ this.migrationModeContainer(view),
+ ]
+ );
+ await view.initializeModel(form.component());
+ }
+
+ public async onPageEnter(): Promise {
+ }
+ public async onPageLeave(): Promise {
+ }
+ protected async handleStateChange(e: StateChangeEvent): Promise {
+ }
+
+ private migrationModeContainer(view: azdata.ModelView): azdata.FormComponent {
+ const buttonGroup = 'cutoverContainer';
+
+ const onlineButton = view.modelBuilder.radioButton().withProps({
+ label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
+ name: buttonGroup,
+ checked: true
+ }).component();
+
+ this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.ONLINE;
+
+ onlineButton.onDidChangeCheckedState((e) => {
+ if (e) {
+ this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.ONLINE;
+ }
+ });
+
+ const offlineButton = view.modelBuilder.radioButton().withProps({
+ label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
+ name: buttonGroup
+ }).component();
+
+ offlineButton.onDidChangeCheckedState((e) => {
+ if (e) {
+ vscode.window.showInformationMessage('Feature coming soon');
+ onlineButton.checked = true;
+ //this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.OFFLINE; TODO: Enable when offline mode is supported.
+ }
+ });
+
+ const flexContainer = view.modelBuilder.flexContainer().withItems(
+ [
+ onlineButton,
+ offlineButton
+ ]
+ ).withLayout({
+ flexFlow: 'column'
+ }).component();
+
+ return {
+ component: flexContainer
+ };
+ }
+}
diff --git a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts
index 7d493b7435..40fa81a035 100644
--- a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts
+++ b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts
@@ -300,8 +300,11 @@ export class SKURecommendationPage extends MigrationWizardPage {
errors.push(constants.INVALID_SUBSCRIPTION_ERROR);
}
const resourceDropdownValue = (this._resourceDropdown.value).displayName;
- if (resourceDropdownValue === constants.NO_MANAGED_INSTANCE_FOUND || resourceDropdownValue === constants.NO_VIRTUAL_MACHINE_FOUND) {
- errors.push(constants.INVALID_STORAGE_ACCOUNT_ERROR);
+ if (resourceDropdownValue === constants.NO_MANAGED_INSTANCE_FOUND) {
+ errors.push(constants.NO_MANAGED_INSTANCE_FOUND);
+ }
+ else if (resourceDropdownValue === constants.NO_VIRTUAL_MACHINE_FOUND) {
+ errors.push(constants.NO_VIRTUAL_MACHINE_FOUND);
}
if (errors.length > 0) {
diff --git a/extensions/sql-migration/src/wizard/summaryPage.ts b/extensions/sql-migration/src/wizard/summaryPage.ts
index e519a353cc..0f0dde87ee 100644
--- a/extensions/sql-migration/src/wizard/summaryPage.ts
+++ b/extensions/sql-migration/src/wizard/summaryPage.ts
@@ -72,12 +72,16 @@ export class SummaryPage extends MigrationWizardPage {
flexContainer.addItems(
[
createInformationRow(this._view, constants.TYPE, constants.NETWORK_SHARE),
- createInformationRow(this._view, constants.PATH, this.migrationStateModel._databaseBackup.networkShareLocation),
createInformationRow(this._view, constants.USER_ACCOUNT, this.migrationStateModel._databaseBackup.windowsUser),
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),
+ createHeadingTextComponent(this._view, 'Target Databases:')
]
);
+ 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_NETWORK_SHARE_LOCATION(db), this.migrationStateModel._databaseBackup.networkShareLocations[index]));
+ });
break;
case NetworkContainerType.FILE_SHARE:
flexContainer.addItems(
@@ -85,19 +89,25 @@ export class SummaryPage extends MigrationWizardPage {
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),
- createInformationRow(this._view, constants.FILE_SHARE, this.migrationStateModel._databaseBackup.fileShare.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.blobContainer.subscription.name),
+ 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.BLOB_CONTAINER, this.migrationStateModel._databaseBackup.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));
+ });
}
return flexContainer;
}
diff --git a/extensions/sql-migration/src/wizard/wizardController.ts b/extensions/sql-migration/src/wizard/wizardController.ts
index 4358818c36..7831a6b9ac 100644
--- a/extensions/sql-migration/src/wizard/wizardController.ts
+++ b/extensions/sql-migration/src/wizard/wizardController.ts
@@ -14,6 +14,7 @@ import { DatabaseBackupPage } from './databaseBackupPage';
import { AccountsSelectionPage } from './accountsSelectionPage';
import { IntergrationRuntimePage } from './integrationRuntimePage';
import { SummaryPage } from './summaryPage';
+import { MigrationModePage } from './migrationModePage';
export const WIZARD_INPUT_COMPONENT_WIDTH = '400px';
export class WizardController {
@@ -36,6 +37,7 @@ export class WizardController {
wizard.generateScriptButton.hidden = true;
const skuRecommendationPage = new SKURecommendationPage(wizard, stateModel);
// const subscriptionSelectionPage = new SubscriptionSelectionPage(wizard, stateModel);
+ const migrationModePage = new MigrationModePage(wizard, stateModel);
const azureAccountsPage = new AccountsSelectionPage(wizard, stateModel);
const databaseBackupPage = new DatabaseBackupPage(wizard, stateModel);
const integrationRuntimePage = new IntergrationRuntimePage(wizard, stateModel);
@@ -45,6 +47,7 @@ export class WizardController {
// subscriptionSelectionPage,
azureAccountsPage,
skuRecommendationPage,
+ migrationModePage,
databaseBackupPage,
integrationRuntimePage,
summaryPage
@@ -114,7 +117,7 @@ export function createHeadingTextComponent(view: azdata.ModelView, value: string
export function creaetLabelTextComponent(view: azdata.ModelView, value: string): azdata.TextComponent {
const component = createTextCompononent(view, value);
component.updateCssStyles({
- 'width': '250px'
+ 'width': '300px'
});
return component;
}