Renaming controller to migration service and other bug fixes/ validations. (#14751)

* - Added coming soon message for learn more.
- Potential fix for learn more message

* Renaming of controller to sqlMigrationService

* Surfacing some errors
-Azure account is stale error
-Migration Service creation error.

* Adding refresh azure token validation.

* Fixing some errors pointed during PR
-Fixing property names
-Fixing count

* Fixing migration status
- Adding special error handling for resource not found error
- Deleting unfound migrations from local cache
- Using prefetched migration status for view all

Misc fixes:
- Using SQL server version name instead of number
- Fixing Icons on sku recommendation page
- Fixing table column width in cutover dialog
- Adding spinner button to refresh.

* Fixing all strings in migration service page and dialog

* fixed a string error in create service dialog
This commit is contained in:
Aasim Khan
2021-03-17 14:55:24 -07:00
committed by GitHub
parent 5917f869ef
commit c7cca7e9f0
16 changed files with 558 additions and 418 deletions

View File

@@ -86,10 +86,10 @@ export async function getBlobContainers(account: azdata.Account, subscription: S
return blobContainers!; return blobContainers!;
} }
export async function getMigrationController(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise<SqlMigrationController> { export async function getSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise<SqlMigrationService> {
const api = await getAzureCoreAPI(); const api = await getAzureCoreAPI();
const host = `https://${regionName}.management.azure.com`; const host = `https://${regionName}.management.azure.com`;
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers/${controllerName}?api-version=2020-09-01-preview`; const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) { if (response.errors.length > 0) {
throw new Error(response.errors.toString()); throw new Error(response.errors.toString());
@@ -97,10 +97,10 @@ export async function getMigrationController(account: azdata.Account, subscripti
return response.response.data; return response.response.data;
} }
export async function getMigrationControllers(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string): Promise<SqlMigrationController[]> { export async function getSqlMigrationServices(account: azdata.Account, subscription: Subscription, regionName: string): Promise<SqlMigrationService[]> {
const api = await getAzureCoreAPI(); const api = await getAzureCoreAPI();
const host = `https://${regionName}.management.azure.com`; const host = `https://${regionName}.management.azure.com`;
const path = `/subscriptions/${subscription.id}/providers/Microsoft.DataMigration/Controllers?api-version=2020-09-01-preview`; const path = `/subscriptions/${subscription.id}/providers/Microsoft.DataMigration/sqlMigrationServices?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) { if (response.errors.length > 0) {
throw new Error(response.errors.toString()); throw new Error(response.errors.toString());
@@ -109,10 +109,10 @@ export async function getMigrationControllers(account: azdata.Account, subscript
return response.response.data.value; return response.response.data.value;
} }
export async function createMigrationController(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise<SqlMigrationController> { export async function createSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise<SqlMigrationService> {
const api = await getAzureCoreAPI(); const api = await getAzureCoreAPI();
const host = `https://${regionName}.management.azure.com`; const host = `https://${regionName}.management.azure.com`;
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers/${controllerName}?api-version=2020-09-01-preview`; const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`;
const requestBody = { const requestBody = {
'location': regionName 'location': regionName
}; };
@@ -123,10 +123,10 @@ export async function createMigrationController(account: azdata.Account, subscri
return response.response.data; return response.response.data;
} }
export async function getMigrationControllerAuthKeys(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise<GetMigrationControllerAuthKeysResult> { export async function getSqlMigrationServiceAuthKeys(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise<SqlMigrationServiceAuthenticationKeys> {
const api = await getAzureCoreAPI(); const api = await getAzureCoreAPI();
const host = `https://${regionName}.management.azure.com`; const host = `https://${regionName}.management.azure.com`;
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers/${controllerName}/ListAuthKeys?api-version=2020-09-01-preview`; const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}/ListAuthKeys?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host);
if (response.errors.length > 0) { if (response.errors.length > 0) {
throw new Error(response.errors.toString()); throw new Error(response.errors.toString());
@@ -150,10 +150,10 @@ export async function getStorageAccountAccessKeys(account: azdata.Account, subsc
}; };
} }
export async function getMigrationControllerMonitoringData(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise<GetMigrationControllerMonitoringData> { export async function getSqlMigrationServiceMonitoringData(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationService: string): Promise<IntegrationRuntimeMonitoringData> {
const api = await getAzureCoreAPI(); const api = await getAzureCoreAPI();
const host = `https://${regionName}.management.azure.com`; const host = `https://${regionName}.management.azure.com`;
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers/${controllerName}/monitoringData?api-version=2020-09-01-preview`; const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationService}/monitoringData?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) { if (response.errors.length > 0) {
throw new Error(response.errors.toString()); throw new Error(response.errors.toString());
@@ -182,6 +182,9 @@ export async function getDatabaseMigration(account: azdata.Account, subscription
const path = `${migrationId}?api-version=2020-09-01-preview`; const path = `${migrationId}?api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) { if (response.errors.length > 0) {
if (response.response.status === 404 && response.response.data.error.code === 'ResourceDoesNotExist') {
throw new Error(response.response.data.error.code);
}
throw new Error(response.errors.toString()); throw new Error(response.errors.toString());
} }
return response.response.data; return response.response.data;
@@ -198,10 +201,10 @@ export async function getMigrationStatus(account: azdata.Account, subscription:
return response.response.data; return response.response.data;
} }
export async function listMigrationsByController(account: azdata.Account, subscription: Subscription, controller: SqlMigrationController): Promise<DatabaseMigration[]> { export async function listMigrationsBySqlMigrationService(account: azdata.Account, subscription: Subscription, sqlMigrationService: SqlMigrationService): Promise<DatabaseMigration[]> {
const api = await getAzureCoreAPI(); const api = await getAzureCoreAPI();
const host = `https://eastus2euap.management.azure.com`; const host = `https://eastus2euap.management.azure.com`;
const path = `${controller.id}/listMigrations?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`; const path = `${sqlMigrationService.id}/listMigrations?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`;
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
if (response.errors.length > 0) { if (response.errors.length > 0) {
throw new Error(response.errors.toString()); throw new Error(response.errors.toString());
@@ -233,7 +236,7 @@ export async function stopMigration(account: azdata.Account, subscription: Subsc
/** /**
* For now only east us euap is supported. Actual API calls will be added in the public release. * For now only east us euap is supported. Actual API calls will be added in the public release.
*/ */
export function getMigrationControllerRegions(): azdata.CategoryValue[] { export function getSqlMigrationServiceRegions(): azdata.CategoryValue[] {
return [ return [
{ {
displayName: loc.EASTUS2EUAP, displayName: loc.EASTUS2EUAP,
@@ -242,7 +245,7 @@ export function getMigrationControllerRegions(): azdata.CategoryValue[] {
]; ];
} }
type SortableAzureResources = AzureProduct | azureResource.FileShare | azureResource.BlobContainer | azureResource.AzureResourceSubscription | SqlMigrationController; type SortableAzureResources = AzureProduct | azureResource.FileShare | azureResource.BlobContainer | azureResource.AzureResourceSubscription | SqlMigrationService;
function sortResourceArrayByName(resourceArray: SortableAzureResources[]): void { function sortResourceArrayByName(resourceArray: SortableAzureResources[]): void {
if (!resourceArray) { if (!resourceArray) {
return; return;
@@ -258,7 +261,7 @@ function sortResourceArrayByName(resourceArray: SortableAzureResources[]): void
}); });
} }
export interface MigrationControllerProperties { export interface SqlMigrationServiceProperties {
name: string; name: string;
subscriptionId: string; subscriptionId: string;
resourceGroup: string; resourceGroup: string;
@@ -268,8 +271,8 @@ export interface MigrationControllerProperties {
isProvisioned?: boolean; isProvisioned?: boolean;
} }
export interface SqlMigrationController { export interface SqlMigrationService {
properties: MigrationControllerProperties; properties: SqlMigrationServiceProperties;
location: string; location: string;
id: string; id: string;
name: string; name: string;
@@ -279,7 +282,7 @@ export interface SqlMigrationController {
} }
} }
export interface GetMigrationControllerAuthKeysResult { export interface SqlMigrationServiceAuthenticationKeys {
authKey1: string, authKey1: string,
authKey2: string authKey2: string
} }
@@ -289,12 +292,12 @@ export interface GetStorageAccountAccessKeysResult {
keyName2: string keyName2: string
} }
export interface GetMigrationControllerMonitoringData { export interface IntegrationRuntimeMonitoringData {
name: string, name: string,
nodes: MigrationControllerNode[]; nodes: IntegrationRuntimeNode[];
} }
export interface MigrationControllerNode { export interface IntegrationRuntimeNode {
availableMemoryInMB: number, availableMemoryInMB: number,
concurrentJobsLimit: number concurrentJobsLimit: number
concurrentJobsRunning: number, concurrentJobsRunning: number,
@@ -307,27 +310,32 @@ export interface MigrationControllerNode {
export interface StartDatabaseMigrationRequest { export interface StartDatabaseMigrationRequest {
location: string, location: string,
properties: { properties: {
SourceDatabaseName: string, sourceDatabaseName: string,
MigrationController: string, migrationService: string,
BackupConfiguration: { backupConfiguration: {
TargetLocation: { targetLocation: {
StorageAccountResourceId: string, storageAccountResourceId: string,
AccountKey: string, accountKey: string,
} }
SourceLocation: { sourceLocation: {
FileShare: { fileShare?: {
Path: string, path: string,
Username: string, username: string,
Password: string, password: string,
},
azureBlob?: {
storageAccountResourceId: string,
accountKey: string,
blobContainerName: string
} }
}, },
}, },
SourceSqlConnection: { sourceSqlConnection: {
DataSource: string, dataSource: string,
Username: string, username: string,
Password: string password: string
}, },
Scope: string scope: string
} }
} }
@@ -347,10 +355,12 @@ export interface DatabaseMigrationProperties {
provisioningState: string; provisioningState: string;
migrationStatus: string; migrationStatus: string;
migrationStatusDetails?: MigrationStatusDetails; migrationStatusDetails?: MigrationStatusDetails;
startedOn: string;
endedOn: string;
sourceSqlConnection: SqlConnectionInfo; sourceSqlConnection: SqlConnectionInfo;
sourceDatabaseName: string; sourceDatabaseName: string;
targetDatabaseCollation: string; targetDatabaseCollation: string;
migrationController: string; migrationService: string;
migrationOperationId: string; migrationOperationId: string;
backupConfiguration: BackupConfiguration; backupConfiguration: BackupConfiguration;
autoCutoverConfiguration: AutoCutoverConfiguration; autoCutoverConfiguration: AutoCutoverConfiguration;

View File

@@ -21,3 +21,22 @@ export function deepClone<T>(obj: T): T {
}); });
return result; return result;
} }
export function getSqlServerName(majorVersion: number): string | undefined {
switch (majorVersion) {
case 10:
return 'SQL Server 2008';
case 11:
return 'SQL Server 2012';
case 12:
return 'SQL Server 2014';
case 13:
return 'SQL Server 2016';
case 14:
return 'SQL Server 2017';
case 15:
return 'SQL Server 2019';
default:
return undefined;
}
}

View File

@@ -22,6 +22,8 @@ export class IconPathHelper {
public static inProgressMigration: IconPath; public static inProgressMigration: IconPath;
public static completedMigration: IconPath; public static completedMigration: IconPath;
public static notStartedMigration: IconPath; public static notStartedMigration: IconPath;
public static sqlVmLogo: IconPath;
public static sqlMiLogo: IconPath;
public static setExtensionContext(context: vscode.ExtensionContext) { public static setExtensionContext(context: vscode.ExtensionContext) {
IconPathHelper.copy = { IconPathHelper.copy = {
@@ -68,5 +70,13 @@ export class IconPathHelper {
light: context.asAbsolutePath('images/cutover.svg'), light: context.asAbsolutePath('images/cutover.svg'),
dark: context.asAbsolutePath('images/cutover.svg') dark: context.asAbsolutePath('images/cutover.svg')
}; };
IconPathHelper.sqlMiLogo = {
light: context.asAbsolutePath('images/sqlMI.svg'),
dark: context.asAbsolutePath('images/sqlMI.svg')
};
IconPathHelper.sqlVmLogo = {
light: context.asAbsolutePath('images/sqlVM.svg'),
dark: context.asAbsolutePath('images/sqlVM.svg')
};
} }
} }

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { AzureAccount } from 'azurecore';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -28,6 +29,8 @@ export const SKU_RECOMMENDATION_SOME_SUCCESSFUL = (migratableCount: number, data
export const SKU_RECOMMENDATION_CHOOSE_A_TARGET = localize('sql.migration.wizard.sku.choose_a_target', "Choose a target Azure SQL"); export const SKU_RECOMMENDATION_CHOOSE_A_TARGET = localize('sql.migration.wizard.sku.choose_a_target', "Choose a target Azure SQL");
export const SKU_RECOMMENDATION_NONE_SUCCESSFUL = localize('sql.migration.sku.none', "Based on the results of our source configuration scans, none of your databases can be migrated to Azure SQL."); export const SKU_RECOMMENDATION_NONE_SUCCESSFUL = localize('sql.migration.sku.none', "Based on the results of our source configuration scans, none of your databases can be migrated to Azure SQL.");
export const SKU_RECOMMENDATION_MI_CARD_TEXT = localize('sql.migration.sku.mi.card.title', "Azure Managed Instance (Microsoft managed)");
export const SKU_RECOMMENDATION_VM_CARD_TEXT = localize('sql.migration.sku.vm.card.title', "Azure SQL Virtual Machine (Customer managed)'");
export const SUBSCRIPTION_SELECTION_PAGE_TITLE = localize('sql.migration.wizard.subscription.title', "Azure Subscription Selection"); export const SUBSCRIPTION_SELECTION_PAGE_TITLE = localize('sql.migration.wizard.subscription.title', "Azure Subscription Selection");
export const SUBSCRIPTION_SELECTION_AZURE_ACCOUNT_TITLE = localize('sql.migration.wizard.subscription.azure.account.title', "Azure Account"); export const SUBSCRIPTION_SELECTION_AZURE_ACCOUNT_TITLE = localize('sql.migration.wizard.subscription.azure.account.title', "Azure Account");
@@ -47,7 +50,9 @@ export function accountLinkedMessage(count: number): string {
return count === 1 ? localize('sql.migration.wizard.account.count.single.message', '{0} account linked', count) : localize('sql.migration.wizard.account.count.multiple.message', '{0} accounts linked', count); return count === 1 ? localize('sql.migration.wizard.account.count.single.message', '{0} account linked', count) : localize('sql.migration.wizard.account.count.multiple.message', '{0} accounts linked', count);
} }
export const AZURE_TENANT = localize('sql.migration.azure.tenant', "Azure AD tenant"); export const AZURE_TENANT = localize('sql.migration.azure.tenant', "Azure AD tenant");
export function ACCOUNT_STALE_ERROR(account: AzureAccount) {
return localize('azure.accounts.accountStaleError', "The access token for selected account '{0}' is no longer valid. Please click the 'Link Account' button and refresh the account or select a different account.", `${account.displayInfo.displayName} (${account.displayInfo.userId})`);
}
// database backup page // database backup page
export const DATABASE_BACKUP_PAGE_TITLE = localize('sql.migration.database.page.title', "Database Backup"); export const DATABASE_BACKUP_PAGE_TITLE = localize('sql.migration.database.page.title', "Database Backup");
@@ -106,50 +111,51 @@ export const ENTER_BLOB_CONTAINER_INFORMATION = localize('sql.migration.blob.con
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"); 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 // integration runtime page
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Migration Controller"); export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Azure Database Migration Service");
export const IR_PAGE_DESCRIPTION = localize('sql.migration.ir.page.description', "A migration controller is an ARM (Azure Resource Manager) resource created in your Azure subscription and it is needed to coordinate and monitor data migration activities. If one already exists in your subscription, you can reuse it here. Alternatively you can create a new one by clicking New. {0}"); export const IR_PAGE_DESCRIPTION = localize('sql.migration.ir.page.description', "Azure Database Migration Service (DMS) orchestrates database migration activities and tracks their progress. You can select an existing DMS for Azure SQL target if you have created one previously or create a new one below. {0}");
export const IR_PAGE_NOTE = localize('sql.migration.ir.page.note', "Note: Migration Controller will run in your Azure subscription in the chosen resource group and does not incur any cost for running it."); export const IR_PAGE_NOTE = localize('sql.migration.ir.page.note', "Note: DMS will run in your Azure subscription in the chosen resource group and does not incur any cost for running it.");
export const SELECT_A_MIGRATION_CONTROLLER = localize('sql.migration.controller', "Select a migration controller"); export const SELECT_A_SQL_MIGRATION_SERVICE = localize('sql.migration.select.a.migration.service', "Select Azure Data Migration Service");
export const DEFAULT_SETUP_BUTTON = localize('sql.migration.default.setup.button', "Setup with defaults: Add migration controller with one click express setup using default options."); export const DEFAULT_SETUP_BUTTON = localize('sql.migration.default.setup.button', "Setup with defaults: Add DMS with one click express setup using default options.");
export const CUSTOM_SETUP_BUTTON = localize('sql.migration.custom.setup.button', "Custom setup: Add migration controller after customizing most options."); export const CUSTOM_SETUP_BUTTON = localize('sql.migration.custom.setup.button', "Custom setup: Add DMS after customizing most options.");
export const MIGRATION_CONTROLLER_NOT_FOUND_ERROR = localize('sql.migration.ir.page.migration.controller.not.found', "No Migration Controllers found. Please create a new one"); export const SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR = localize('sql.migration.ir.page.sql.migration.service.not.found', "No DMS found. Please create a new one");
export const CREATE_NEW = localize('sql.migration.create.new', "Create new"); export const CREATE_NEW = localize('sql.migration.create.new', "Create new");
export const INVALID_CONTROLLER_ERROR = localize('sql.migration.invalid.controller.error', "Please select a valid controller"); export const INVALID_SERVICE_ERROR = localize('sql.migration.invalid.migration.service.error', "Please select a valid DMS");
export const CONTROLLER_OFFLINE_ERROR = localize('sql.migration.invalid.controller.offline.error', "Please select a controller that is connected to a node"); export const SERVICE_OFFLINE_ERROR = localize('sql.migration.invalid.migration.service.offline.error', "Please select a DMS that is connected to a node");
export const AUTHENTICATION_KEYS = localize('sql.migration.authentication.types', "Authentication Keys"); export const AUTHENTICATION_KEYS = localize('sql.migration.authentication.types', "Authentication Keys");
export function CONTROLLER_DETAILS_HEADER(controllerName: string) { export function SQL_MIGRATION_SERVICE_DETAILS_HEADER(sqlMigrationServiceName: string) {
return localize('sql.migration.controller.header', "Migration Controller \"{0}\" details:`", controllerName); return localize('sql.migration.service.header', "Azure Data Migration Service \"{0}\" details:`", sqlMigrationServiceName);
} }
// create migration controller dialog // create migration service dialog
export const CONTROLLER_DIALOG_DESCRIPTION = localize('sql.migration.controller.container.description', "A migration controller is an ARM (Azure Resource Manager) resource created in your Azure subscription and it is needed to coordinate and monitor data migration activities. {0}"); export const CREATE_MIGRATION_SERVICE_TITLE = localize('sql.migration.services.dialog.title', "Create Azure Data Migration Service");
export const CONTROLLER_DIALOG_CONTROLLER_CONTAINER_LOADING_HELP = localize('sql.migration.controller.container.loading.help', "Loading Controller"); export const MIGRATION_SERVICE_DIALOG_DESCRIPTION = localize('sql.migration.services.container.description', "Enter the information below to add a new Azure Data Migration Service.");
export const CONTROLLER_DIALOG_CREATE_CONTROLLER_FORM_HEADING = localize('sql.migration.controller.dialog.create.controller.form.heading', "Enter the information below to add a new migration controller."); export const LOADING_MIGRATION_SERVICES = localize('sql.migration.service.container.loading.help', "Loading Migration Services");
export const CONTROLLER_DIALOG_CONTROLLER_CONTAINER_HEADING = localize('sql.migration.controller.container.heading', "Setup Integration Runtime"); export const CREATE_SERVICE_FORM_HEADING = localize('sql.migration.service.dialog.create.service.form.heading', "Enter the information below to add a new Migration Service.");
export const CONTROLLER_DIALOG_CONTROLLER_CONTAINER_DESCRIPTION = localize('sql.migration.controller.container.container.description', "Follow the instructions below to setup self-hosted Integration Runtime."); export const SERVICE_CONTAINER_HEADING = localize('sql.migration.service.container.heading', "Setup Integration Runtime");
export const CONTROLLER_STEP1 = localize('sql.migration.ir.setup.step1', "Step 1: {0}"); export const SERVICE_CONTAINER_DESCRIPTION = localize('sql.migration.service.container.container.description', "Follow the instructions below to setup self-hosted Integration Runtime.");
export const CONTROLLER_STEP1_LINK = localize('sql.migration.option', "Download and install integration runtime"); export const SERVICE_STEP1 = localize('sql.migration.ir.setup.step1', "Step 1: {0}");
export const CONTROLLER_STEP2 = localize('sql.migration.ir.setup.step2', "Step 2: Use this key to register your integration runtime"); export const SERVICE_STEP1_LINK = localize('sql.migration.option', "Download and install integration runtime");
export const CONTROLLER_STEP3 = localize('sql.migration.ir.setup.step3', "Step 3: Check connection"); export const SERVICE_STEP2 = localize('sql.migration.ir.setup.step2', "Step 2: Use this key to register your integration runtime");
export const CONTROLLER_CONNECTION_STATUS = localize('sql.migration.connection.status', "Connection Status"); export const SERVICE_STEP3 = localize('sql.migration.ir.setup.step3', "Step 3: Check connection");
export const CONTROLLER_KEY1_LABEL = localize('sql.migration.key1.label', "Key 1"); export const SERVICE_CONNECTION_STATUS = localize('sql.migration.connection.status', "Connection Status");
export const CONTROLLER_KEY2_LABEL = localize('sql.migration.key2.label', "Key 2"); export const SERVICE_KEY1_LABEL = localize('sql.migration.key1.label', "Key 1");
export const CONTROLLER_KEY_COPIED_HELP = localize('sql.migration.key.copied', "Key copied"); export const SERVICE_KEY2_LABEL = localize('sql.migration.key2.label', "Key 2");
export const SERVICE_KEY_COPIED_HELP = localize('sql.migration.key.copied', "Key copied");
export const REFRESH_KEYS = localize('sql.migration.refresh.keys', "Refresh keys"); export const REFRESH_KEYS = localize('sql.migration.refresh.keys', "Refresh keys");
export const COPY_KEY = localize('sql.migration.copy.key', "Copy key"); export const COPY_KEY = localize('sql.migration.copy.key', "Copy key");
export const AUTH_KEY_COLUMN_HEADER = localize('sql.migration.authkeys.header', "Authentication key"); export const AUTH_KEY_COLUMN_HEADER = localize('sql.migration.authkeys.header', "Authentication key");
export function CONTROLLER_NOT_READY(controllerName: string): string { export function SERVICE_NOT_READY(serviceName: string): string {
return localize('sql.migration.controller.not.ready', "Migration Controller {0} is not connected to self-hosted Integration Runtime on any node.", controllerName); return localize('sql.migration.service.not.ready', "Azure Data Migration Service is not registered. Azure Data Migration Service '{0}' needs to be registered with self-hosted Integration Runtime on any node.", serviceName);
} }
export function CONTROLLER_READY(controllerName: string, host: string): string { export function SERVICE_READY(serviceName: string, host: string): string {
return localize('sql.migration.controller.ready', "Migration Controller '{0}' is connected to self-hosted Integration Runtime on the node - {1}", controllerName, host); return localize('sql.migration.service.ready', "Azure Data Migration Service '{0}' is connected to self-hosted Integration Runtime running on the node - {1}", serviceName, host);
} }
export const RESOURCE_GROUP_NOT_FOUND = localize('sql.migration.resource.group.not.found', "No resource Groups found"); export const RESOURCE_GROUP_NOT_FOUND = localize('sql.migration.resource.group.not.found', "No resource Groups found");
export const INVALID_RESOURCE_GROUP_ERROR = localize('sql.migration.invalid.resourceGroup.error', "Please select a valid resource group to proceed."); export const INVALID_RESOURCE_GROUP_ERROR = localize('sql.migration.invalid.resourceGroup.error', "Please select a valid resource group to proceed.");
export const INVALID_REGION_ERROR = localize('sql.migration.invalid.region.error', "Please select a valid region to proceed."); export const INVALID_REGION_ERROR = localize('sql.migration.invalid.region.error', "Please select a valid region to proceed.");
export const INVALID_CONTROLLER_NAME_ERROR = localize('sql.migration.invalid.controller.name.error', "Please enter a valid name for the migration controller."); export const INVALID_SERVICE_NAME_ERROR = localize('sql.migration.invalid.service.name.error', "Please enter a valid name for the Migration Service.");
export const CONTROLLER_NOT_FOUND = localize('sql.migration.controller.not.found', "No Migration Controllers found. Please create a new one."); export const SERVICE_NOT_FOUND = localize('sql.migration.service.not.found', "No Migration Services found. Please create a new one.");
export const CONTROLLER_NOT_SETUP_ERROR = localize('sql.migration.controller.not.setup', "Please add a migration controller to proceed."); export const SERVICE_NOT_SETUP_ERROR = localize('sql.migration.service.not.setup', "Please add a Migration Service to proceed.");
export const MANAGED_INSTANCE = localize('sql.migration.managed.instance', "Azure SQL managed instance"); export const MANAGED_INSTANCE = localize('sql.migration.managed.instance', "Azure SQL managed instance");
export const NO_MANAGED_INSTANCE_FOUND = localize('sql.migration.no.managedInstance.found', "No managed instance found"); export const NO_MANAGED_INSTANCE_FOUND = localize('sql.migration.no.managedInstance.found', "No managed instance found");
export const NO_VIRTUAL_MACHINE_FOUND = localize('sql.migration.no.virtualMachine.found', "No virtual machine found"); export const NO_VIRTUAL_MACHINE_FOUND = localize('sql.migration.no.virtualMachine.found', "No virtual machine found");
@@ -215,12 +221,11 @@ export const MIGRATION_COMPLETED = localize('sql.migration.migration.completed',
export const SUCCESSFULLY_MIGRATED_TO_AZURE_SQL = localize('sql.migration.successfully.migrated.to.azure.sql', "Successfully migrated to Azure SQL"); export const SUCCESSFULLY_MIGRATED_TO_AZURE_SQL = localize('sql.migration.successfully.migrated.to.azure.sql', "Successfully migrated to Azure SQL");
export const MIGRATION_NOT_STARTED = localize('sql.migration.migration.not.started', "Migration not started"); export const MIGRATION_NOT_STARTED = localize('sql.migration.migration.not.started', "Migration not started");
export const CHOOSE_TO_MIGRATE_TO_AZURE_SQL = localize('sql.migration.choose.to.migrate.to.azure.sql', "Choose to migrate to Azure SQL"); export const CHOOSE_TO_MIGRATE_TO_AZURE_SQL = localize('sql.migration.choose.to.migrate.to.azure.sql', "Choose to migrate to Azure SQL");
export const COMING_SOON = localize('sql.migration.coming.soon', "Coming soon");
// Azure APIs // Azure APIs
export const EASTUS2EUAP = localize('sql.migration.eastus2euap', 'East US 2 EUAP'); export const EASTUS2EUAP = localize('sql.migration.eastus2euap', 'East US 2 EUAP');
//Migration cutover dialog //Migration cutover dialog
export const MIGRATION_CUTOVER = localize('sql.migration.cutover', "Migration cutover"); export const MIGRATION_CUTOVER = localize('sql.migration.cutover', "Migration cutover");
export const SOURCE_SERVER = localize('sql.migration.source.server', "Source server"); export const SOURCE_SERVER = localize('sql.migration.source.server', "Source server");
@@ -253,6 +258,7 @@ export function ACTIVE_BACKUP_FILES_ITEMS(fileCount: number) {
//Migration status dialog //Migration status dialog
export const SEARCH_FOR_MIGRATIONS = localize('sql.migration.search.for.migration', "Search for migrations"); export const SEARCH_FOR_MIGRATIONS = localize('sql.migration.search.for.migration', "Search for migrations");
export const ONLINE = localize('sql.migration.online', "Online"); export const ONLINE = localize('sql.migration.online', "Online");
export const OFFLINE = localize('sql.migration.offline', "Offline");
export const DATABASE = localize('sql.migration.database', "Database"); export const DATABASE = localize('sql.migration.database', "Database");
export const TARGET_AZURE_SQL_INSTANCE_NAME = localize('sql.migration.target.azure.sql.instance.name', "Target Azure SQL Instance Name"); export const TARGET_AZURE_SQL_INSTANCE_NAME = localize('sql.migration.target.azure.sql.instance.name', "Target Azure SQL Instance Name");
export const CUTOVER_TYPE = localize('sql.migration.cutover.type', "Cutover type"); export const CUTOVER_TYPE = localize('sql.migration.cutover.type', "Cutover type");

View File

@@ -8,7 +8,6 @@ import * as vscode from 'vscode';
import { MigrationContext, MigrationLocalStorage } from '../models/migrationLocalStorage'; import { MigrationContext, MigrationLocalStorage } from '../models/migrationLocalStorage';
import * as loc from '../constants/strings'; import * as loc from '../constants/strings';
import { IconPath, IconPathHelper } from '../constants/iconPathHelper'; import { IconPath, IconPathHelper } from '../constants/iconPathHelper';
import { getDatabaseMigration } from '../api/azure';
import { MigrationStatusDialog } from '../dialog/migrationStatus/migrationStatusDialog'; import { MigrationStatusDialog } from '../dialog/migrationStatus/migrationStatusDialog';
import { MigrationCategory } from '../dialog/migrationStatus/migrationStatusDialogModel'; import { MigrationCategory } from '../dialog/migrationStatus/migrationStatusDialogModel';
@@ -22,12 +21,25 @@ interface IActionMetadata {
const maxWidth = 800; const maxWidth = 800;
interface StatusCard {
container: azdata.DivContainer;
count: azdata.TextComponent
}
export class DashboardWidget { export class DashboardWidget {
private _migrationStatusCardsContainer!: azdata.FlexContainer; private _migrationStatusCardsContainer!: azdata.FlexContainer;
private _migrationStatusCardLoadingContainer!: azdata.LoadingComponent; private _migrationStatusCardLoadingContainer!: azdata.LoadingComponent;
private _view!: azdata.ModelView; private _view!: azdata.ModelView;
private _inProgressMigrationButton!: StatusCard;
private _successfulMigrationButton!: StatusCard;
private _notStartedMigrationCard!: StatusCard;
private _migrationStatus!: MigrationContext[];
private _viewAllMigrationsButton!: azdata.ButtonComponent;
constructor() { constructor() {
} }
@@ -143,6 +155,10 @@ export class DashboardWidget {
} }
}).component(); }).component();
preRequisiteLearnMoreLink.onDidClick((value) => {
vscode.window.showInformationMessage(loc.COMING_SOON);
});
const preReqContainer = view.modelBuilder.flexContainer().withItems([ const preReqContainer = view.modelBuilder.flexContainer().withItems([
preRequisiteListTitle, preRequisiteListTitle,
preRequisiteListElement preRequisiteListElement
@@ -197,101 +213,51 @@ export class DashboardWidget {
} }
private async refreshMigrations(): Promise<void> { private async refreshMigrations(): Promise<void> {
this._viewAllMigrationsButton.enabled = false;
this._migrationStatusCardLoadingContainer.loading = true; this._migrationStatusCardLoadingContainer.loading = true;
this._migrationStatusCardsContainer.clearItems();
try { try {
const migrationStatus = await this.getMigrations(); this._migrationStatus = await this.getMigrations();
const inProgressMigrations = migrationStatus.filter((value) => { const inProgressMigrations = this._migrationStatus.filter((value) => {
const status = value.migrationContext.properties.migrationStatus; const status = value.migrationContext.properties.migrationStatus;
return status === 'InProgress' || status === 'Creating' || status === 'Completing'; return status === 'InProgress' || status === 'Creating' || status === 'Completing';
}); });
const inProgressMigrationButton = this.createStatusCard(
IconPathHelper.inProgressMigration,
loc.MIGRATION_IN_PROGRESS,
loc.LOG_SHIPPING_IN_PROGRESS,
inProgressMigrations.length
);
inProgressMigrationButton.onDidClick((e) => {
const dialog = new MigrationStatusDialog(migrationStatus, MigrationCategory.ONGOING);
dialog.initialize();
});
this._migrationStatusCardsContainer.addItem(inProgressMigrationButton);
const successfulMigration = migrationStatus.filter((value) => { this._inProgressMigrationButton.count.value = inProgressMigrations.length.toString();
const successfulMigration = this._migrationStatus.filter((value) => {
const status = value.migrationContext.properties.migrationStatus; const status = value.migrationContext.properties.migrationStatus;
return status === 'Succeeded'; return status === 'Succeeded';
}); });
const successfulMigrationButton = this.createStatusCard(
IconPathHelper.completedMigration, this._successfulMigrationButton.count.value = successfulMigration.length.toString();
loc.MIGRATION_COMPLETED,
loc.SUCCESSFULLY_MIGRATED_TO_AZURE_SQL,
successfulMigration.length
);
successfulMigrationButton.onDidClick((e) => {
const dialog = new MigrationStatusDialog(migrationStatus, MigrationCategory.SUCCEEDED);
dialog.initialize();
});
this._migrationStatusCardsContainer.addItem(
successfulMigrationButton
);
const currentConnection = (await azdata.connection.getCurrentConnection()); const currentConnection = (await azdata.connection.getCurrentConnection());
const localMigrations = MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection);
const migrationDatabases = new Set( const migrationDatabases = new Set(
localMigrations.filter((value) => { this._migrationStatus.map((value) => {
}).map((value) => {
return value.migrationContext.properties.sourceDatabaseName; return value.migrationContext.properties.sourceDatabaseName;
})); }));
const serverDatabases = await azdata.connection.listDatabases(currentConnection.connectionId); const serverDatabases = await azdata.connection.listDatabases(currentConnection.connectionId);
const notStartedMigrationCard = this.createStatusCard( this._notStartedMigrationCard.count.value = (serverDatabases.length - migrationDatabases.size).toString();
IconPathHelper.notStartedMigration,
loc.MIGRATION_NOT_STARTED,
loc.CHOOSE_TO_MIGRATE_TO_AZURE_SQL,
serverDatabases.length - migrationDatabases.size
);
notStartedMigrationCard.onDidClick((e) => {
vscode.window.showInformationMessage('Feature coming soon');
});
this._migrationStatusCardsContainer.addItem(
notStartedMigrationCard
);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
this._migrationStatusCardLoadingContainer.loading = false; this._migrationStatusCardLoadingContainer.loading = false;
this._viewAllMigrationsButton.enabled = true;
} }
} }
private async getMigrations(): Promise<MigrationContext[]> { private async getMigrations(): Promise<MigrationContext[]> {
const currentConnection = (await azdata.connection.getCurrentConnection()); const currentConnection = (await azdata.connection.getCurrentConnection());
const localMigrations = MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection); return await MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection, true);
for (let i = 0; i < localMigrations.length; i++) {
const localMigration = localMigrations[i];
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;
} }
private createStatusCard( private createStatusCard(
cardIconPath: IconPath, cardIconPath: IconPath,
cardTitle: string, cardTitle: string,
cardDescription: string, cardDescription: string
count: number ): StatusCard {
): azdata.DivContainer {
const cardTitleText = this._view.modelBuilder.text().withProps({ value: cardTitle }).withProps({ const cardTitleText = this._view.modelBuilder.text().withProps({ value: cardTitle }).withProps({
CSSStyles: { CSSStyles: {
@@ -312,7 +278,7 @@ export class DashboardWidget {
} }
}).component(); }).component();
const cardCount = this._view.modelBuilder.text().withProps({ const cardCount = this._view.modelBuilder.text().withProps({
value: count.toString(), value: '0',
CSSStyles: { CSSStyles: {
'font-size': '28px', 'font-size': '28px',
'line-height': '36px', 'line-height': '36px',
@@ -367,7 +333,10 @@ export class DashboardWidget {
ariaLabel: 'show status', ariaLabel: 'show status',
clickable: true clickable: true
}).component(); }).component();
return compositeButton; return {
container: compositeButton,
count: cardCount
};
} }
private async createFooter(view: azdata.ModelView): Promise<azdata.Component> { private async createFooter(view: azdata.ModelView): Promise<azdata.Component> {
@@ -412,7 +381,7 @@ export class DashboardWidget {
} }
}).component(); }).component();
const viewAllButton = view.modelBuilder.hyperlink().withProps({ this._viewAllMigrationsButton = view.modelBuilder.hyperlink().withProps({
label: loc.VIEW_ALL, label: loc.VIEW_ALL,
url: '', url: '',
CSSStyles: { CSSStyles: {
@@ -420,8 +389,8 @@ export class DashboardWidget {
} }
}).component(); }).component();
viewAllButton.onDidClick(async (e) => { this._viewAllMigrationsButton.onDidClick(async (e) => {
new MigrationStatusDialog(await this.getMigrations(), MigrationCategory.ALL).initialize(); new MigrationStatusDialog(this._migrationStatus ? this._migrationStatus : await this.getMigrations(), MigrationCategory.ALL).initialize();
}); });
const refreshButton = view.modelBuilder.hyperlink().withProps({ const refreshButton = view.modelBuilder.hyperlink().withProps({
@@ -433,15 +402,17 @@ export class DashboardWidget {
} }
}).component(); }).component();
refreshButton.onDidClick((e) => { refreshButton.onDidClick(async (e) => {
this.refreshMigrations(); refreshButton.enabled = false;
await this.refreshMigrations();
refreshButton.enabled = true;
}); });
const buttonContainer = view.modelBuilder.flexContainer().withLayout({ const buttonContainer = view.modelBuilder.flexContainer().withLayout({
justifyContent: 'flex-end', justifyContent: 'flex-end',
}).component(); }).component();
buttonContainer.addItem(viewAllButton, { buttonContainer.addItem(this._viewAllMigrationsButton, {
flex: 'auto', flex: 'auto',
CSSStyles: { CSSStyles: {
'border-right': '1px solid ', 'border-right': '1px solid ',
@@ -466,9 +437,46 @@ export class DashboardWidget {
flexFlow: 'row' flexFlow: 'row'
}).component(); }).component();
this._migrationStatusCardsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); this._migrationStatusCardsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
this._inProgressMigrationButton = this.createStatusCard(
IconPathHelper.inProgressMigration,
loc.MIGRATION_IN_PROGRESS,
loc.LOG_SHIPPING_IN_PROGRESS
);
this._inProgressMigrationButton.container.onDidClick((e) => {
const dialog = new MigrationStatusDialog(this._migrationStatus, MigrationCategory.ONGOING);
dialog.initialize();
});
this._migrationStatusCardsContainer.addItem(
this._inProgressMigrationButton.container
);
this._successfulMigrationButton = this.createStatusCard(
IconPathHelper.completedMigration,
loc.MIGRATION_COMPLETED,
loc.SUCCESSFULLY_MIGRATED_TO_AZURE_SQL
);
this._successfulMigrationButton.container.onDidClick((e) => {
const dialog = new MigrationStatusDialog(this._migrationStatus, MigrationCategory.SUCCEEDED);
dialog.initialize();
});
this._migrationStatusCardsContainer.addItem(
this._successfulMigrationButton.container
);
this._notStartedMigrationCard = this.createStatusCard(
IconPathHelper.notStartedMigration,
loc.MIGRATION_NOT_STARTED,
loc.CHOOSE_TO_MIGRATE_TO_AZURE_SQL
);
this._notStartedMigrationCard.container.onDidClick((e) => {
vscode.window.showInformationMessage('Feature coming soon');
});
this._migrationStatusCardsContainer.addItem(
this._notStartedMigrationCard.container
);
this._migrationStatusCardLoadingContainer = view.modelBuilder.loadingComponent().withItem(this._migrationStatusCardsContainer).component(); this._migrationStatusCardLoadingContainer = view.modelBuilder.loadingComponent().withItem(this._migrationStatusCardsContainer).component();
statusContainer.addItem( statusContainer.addItem(

View File

@@ -5,7 +5,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { createMigrationController, getMigrationControllerRegions, getMigrationController, getResourceGroups, getMigrationControllerAuthKeys, getMigrationControllerMonitoringData } from '../../api/azure'; import { createSqlMigrationService, getSqlMigrationServiceRegions, getSqlMigrationService, getResourceGroups, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData, SqlMigrationService } from '../../api/azure';
import { MigrationStateModel } from '../../models/stateMachine'; import { MigrationStateModel } from '../../models/stateMachine';
import * as constants from '../../constants/strings'; import * as constants from '../../constants/strings';
import * as os from 'os'; import * as os from 'os';
@@ -13,16 +13,16 @@ import { azureResource } from 'azureResource';
import { IntergrationRuntimePage } from '../../wizard/integrationRuntimePage'; import { IntergrationRuntimePage } from '../../wizard/integrationRuntimePage';
import { IconPathHelper } from '../../constants/iconPathHelper'; import { IconPathHelper } from '../../constants/iconPathHelper';
export class CreateMigrationControllerDialog { export class CreateSqlMigrationServiceDialog {
private migrationControllerSubscriptionDropdown!: azdata.DropDownComponent; private migrationServiceSubscriptionDropdown!: azdata.DropDownComponent;
private migrationControllerResourceGroupDropdown!: azdata.DropDownComponent; private migrationServiceResourceGroupDropdown!: azdata.DropDownComponent;
private migrationControllerRegionDropdown!: azdata.DropDownComponent; private migrationServiceRegionDropdown!: azdata.DropDownComponent;
private migrationControllerNameText!: azdata.InputBoxComponent; private migrationServiceNameText!: azdata.InputBoxComponent;
private _formSubmitButton!: azdata.ButtonComponent; private _formSubmitButton!: azdata.ButtonComponent;
private _statusLoadingComponent!: azdata.LoadingComponent; private _statusLoadingComponent!: azdata.LoadingComponent;
private migrationControllerAuthKeyTable!: azdata.DeclarativeTableComponent; private migrationServiceAuthKeyTable!: azdata.DeclarativeTableComponent;
private _connectionStatus!: azdata.InfoBoxComponent; private _connectionStatus!: azdata.InfoBoxComponent;
private _copyKey1Button!: azdata.ButtonComponent; private _copyKey1Button!: azdata.ButtonComponent;
private _copyKey2Button!: azdata.ButtonComponent; private _copyKey2Button!: azdata.ButtonComponent;
@@ -33,8 +33,11 @@ export class CreateMigrationControllerDialog {
private _dialogObject!: azdata.window.Dialog; private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView; private _view!: azdata.ModelView;
private createdMigrationService!: SqlMigrationService;
private createdMigrationServiceNodeNames!: string[];
constructor(private migrationStateModel: MigrationStateModel, private irPage: IntergrationRuntimePage) { constructor(private migrationStateModel: MigrationStateModel, private irPage: IntergrationRuntimePage) {
this._dialogObject = azdata.window.createModelViewDialog(constants.IR_PAGE_TITLE, 'MigrationControllerDialog', 'medium'); this._dialogObject = azdata.window.createModelViewDialog(constants.CREATE_MIGRATION_SERVICE_TITLE, 'MigrationServiceDialog', 'medium');
} }
initialize() { initialize() {
@@ -51,15 +54,18 @@ export class CreateMigrationControllerDialog {
}).component(); }).component();
this._formSubmitButton.onDidClick(async (e) => { this._formSubmitButton.onDidClick(async (e) => {
this._dialogObject.message = {
text: ''
};
this._statusLoadingComponent.loading = true; this._statusLoadingComponent.loading = true;
this._formSubmitButton.enabled = false; this._formSubmitButton.enabled = false;
const subscription = this.migrationStateModel._targetSubscription; const subscription = this.migrationStateModel._targetSubscription;
const resourceGroup = (this.migrationControllerResourceGroupDropdown.value as azdata.CategoryValue).name; const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name;
const region = (this.migrationControllerRegionDropdown.value as azdata.CategoryValue).name; const region = (this.migrationServiceRegionDropdown.value as azdata.CategoryValue).name;
const controllerName = this.migrationControllerNameText.value; const serviceName = this.migrationServiceNameText.value;
const formValidationErrors = this.validateCreateControllerForm(subscription, resourceGroup, region, controllerName); const formValidationErrors = this.validateCreateServiceForm(subscription, resourceGroup, region, serviceName);
if (formValidationErrors.length > 0) { if (formValidationErrors.length > 0) {
this.setDialogMessage(formValidationErrors); this.setDialogMessage(formValidationErrors);
@@ -69,9 +75,9 @@ export class CreateMigrationControllerDialog {
} }
try { try {
const createdController = await createMigrationController(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, controllerName!); this.createdMigrationService = await createSqlMigrationService(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, serviceName!);
if (createdController.error) { if (this.createdMigrationService.error) {
this.setDialogMessage(`${createdController.error.code} : ${createdController.error.message}`); this.setDialogMessage(`${this.createdMigrationService.error.code} : ${this.createdMigrationService.error.message}`);
this._statusLoadingComponent.loading = false; this._statusLoadingComponent.loading = false;
this._formSubmitButton.enabled = true; this._formSubmitButton.enabled = true;
return; return;
@@ -79,13 +85,13 @@ export class CreateMigrationControllerDialog {
this._dialogObject.message = { this._dialogObject.message = {
text: '' text: ''
}; };
this.migrationStateModel._migrationController = createdController;
await this.refreshAuthTable(); await this.refreshAuthTable();
await this.refreshStatus(); await this.refreshStatus();
this._setupContainer.display = 'inline'; this._setupContainer.display = 'inline';
this._statusLoadingComponent.loading = false; this._statusLoadingComponent.loading = false;
} catch (e) { } catch (e) {
console.log(e); console.log(e);
this.setDialogMessage(e.message);
this._statusLoadingComponent.loading = false; this._statusLoadingComponent.loading = false;
this._formSubmitButton.enabled = true; this._formSubmitButton.enabled = true;
return; return;
@@ -93,16 +99,16 @@ export class CreateMigrationControllerDialog {
}); });
this._statusLoadingComponent = view.modelBuilder.loadingComponent().withProps({ this._statusLoadingComponent = view.modelBuilder.loadingComponent().withProps({
loadingText: constants.CONTROLLER_DIALOG_CONTROLLER_CONTAINER_LOADING_HELP, loadingText: constants.LOADING_MIGRATION_SERVICES,
loading: false loading: false
}).component(); }).component();
const creationStatusContainer = this.createControllerStatus(); const creationStatusContainer = this.createServiceStatus();
const formBuilder = view.modelBuilder.formContainer().withFormItems( const formBuilder = view.modelBuilder.formContainer().withFormItems(
[ [
{ {
component: this.migrationControllerDropdownsContainer() component: this.migrationServiceDropdownContainer()
}, },
{ {
component: this._formSubmitButton component: this._formSubmitButton
@@ -132,36 +138,30 @@ export class CreateMigrationControllerDialog {
this._dialogObject.cancelButton.onClick((e) => { this._dialogObject.cancelButton.onClick((e) => {
}); });
this._dialogObject.okButton.onClick((e) => { this._dialogObject.okButton.onClick((e) => {
this.irPage.populateMigrationController(); this.irPage.populateMigrationService(this.createdMigrationService, this.createdMigrationServiceNodeNames);
}); });
} }
private migrationControllerDropdownsContainer(): azdata.FlexContainer { private migrationServiceDropdownContainer(): azdata.FlexContainer {
const dialogDescription = this._view.modelBuilder.text().withProps({ const dialogDescription = this._view.modelBuilder.text().withProps({
value: constants.IR_PAGE_DESCRIPTION, value: constants.MIGRATION_SERVICE_DIALOG_DESCRIPTION
links: [
{
text: constants.LEARN_MORE,
url: 'https://www.microsoft.com' // TODO: add a proper link to the docs.
}
]
}).component(); }).component();
const formHeading = this._view.modelBuilder.text().withProps({ const formHeading = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_DIALOG_CREATE_CONTROLLER_FORM_HEADING value: constants.CREATE_SERVICE_FORM_HEADING
}).component(); }).component();
const subscriptionDropdownLabel = this._view.modelBuilder.text().withProps({ const subscriptionDropdownLabel = this._view.modelBuilder.text().withProps({
value: constants.SUBSCRIPTION value: constants.SUBSCRIPTION
}).component(); }).component();
this.migrationControllerSubscriptionDropdown = this._view.modelBuilder.dropDown().withProps({ this.migrationServiceSubscriptionDropdown = this._view.modelBuilder.dropDown().withProps({
required: true, required: true,
enabled: false enabled: false
}).component(); }).component();
this.migrationControllerSubscriptionDropdown.onValueChanged((e) => { this.migrationServiceSubscriptionDropdown.onValueChanged((e) => {
if (this.migrationControllerSubscriptionDropdown.value) { if (this.migrationServiceSubscriptionDropdown.value) {
this.populateResourceGroups(); this.populateResourceGroups();
} }
}); });
@@ -170,43 +170,43 @@ export class CreateMigrationControllerDialog {
value: constants.RESOURCE_GROUP value: constants.RESOURCE_GROUP
}).component(); }).component();
this.migrationControllerResourceGroupDropdown = this._view.modelBuilder.dropDown().withProps({ this.migrationServiceResourceGroupDropdown = this._view.modelBuilder.dropDown().withProps({
required: true required: true
}).component(); }).component();
const controllerNameLabel = this._view.modelBuilder.text().withProps({ const migrationServiceNameLabel = this._view.modelBuilder.text().withProps({
value: constants.NAME value: constants.NAME
}).component(); }).component();
this.migrationControllerNameText = this._view.modelBuilder.inputBox().component(); this.migrationServiceNameText = this._view.modelBuilder.inputBox().component();
const regionsDropdownLabel = this._view.modelBuilder.text().withProps({ const regionsDropdownLabel = this._view.modelBuilder.text().withProps({
value: constants.REGION value: constants.REGION
}).component(); }).component();
this.migrationControllerRegionDropdown = this._view.modelBuilder.dropDown().withProps({ this.migrationServiceRegionDropdown = this._view.modelBuilder.dropDown().withProps({
required: true, required: true,
values: getMigrationControllerRegions() values: getSqlMigrationServiceRegions()
}).component(); }).component();
const flexContainer = this._view.modelBuilder.flexContainer().withItems([ const flexContainer = this._view.modelBuilder.flexContainer().withItems([
dialogDescription, dialogDescription,
formHeading, formHeading,
subscriptionDropdownLabel, subscriptionDropdownLabel,
this.migrationControllerSubscriptionDropdown, this.migrationServiceSubscriptionDropdown,
resourceGroupDropdownLabel, resourceGroupDropdownLabel,
this.migrationControllerResourceGroupDropdown, this.migrationServiceResourceGroupDropdown,
controllerNameLabel, migrationServiceNameLabel,
this.migrationControllerNameText, this.migrationServiceNameText,
regionsDropdownLabel, regionsDropdownLabel,
this.migrationControllerRegionDropdown this.migrationServiceRegionDropdown
]).withLayout({ ]).withLayout({
flexFlow: 'column' flexFlow: 'column'
}).component(); }).component();
return flexContainer; return flexContainer;
} }
private validateCreateControllerForm(subscription: azureResource.AzureResourceSubscription, resourceGroup: string | undefined, region: string | undefined, controllerName: string | undefined): string { private validateCreateServiceForm(subscription: azureResource.AzureResourceSubscription, resourceGroup: string | undefined, region: string | undefined, migrationServiceName: string | undefined): string {
const errors: string[] = []; const errors: string[] = [];
if (!subscription) { if (!subscription) {
errors.push(constants.INVALID_SUBSCRIPTION_ERROR); errors.push(constants.INVALID_SUBSCRIPTION_ERROR);
@@ -217,29 +217,29 @@ export class CreateMigrationControllerDialog {
if (!region) { if (!region) {
errors.push(constants.INVALID_REGION_ERROR); errors.push(constants.INVALID_REGION_ERROR);
} }
if (!controllerName || controllerName.length === 0) { if (!migrationServiceName || migrationServiceName.length === 0) {
errors.push(constants.INVALID_CONTROLLER_NAME_ERROR); errors.push(constants.INVALID_SERVICE_NAME_ERROR);
} }
return errors.join(os.EOL); return errors.join(os.EOL);
} }
private async populateSubscriptions(): Promise<void> { private async populateSubscriptions(): Promise<void> {
this.migrationControllerSubscriptionDropdown.loading = true; this.migrationServiceSubscriptionDropdown.loading = true;
this.migrationControllerResourceGroupDropdown.loading = true; this.migrationServiceResourceGroupDropdown.loading = true;
this.migrationControllerSubscriptionDropdown.values = [ this.migrationServiceSubscriptionDropdown.values = [
{ {
displayName: this.migrationStateModel._targetSubscription.name, displayName: this.migrationStateModel._targetSubscription.name,
name: '' name: ''
} }
]; ];
this.migrationControllerSubscriptionDropdown.loading = false; this.migrationServiceSubscriptionDropdown.loading = false;
this.populateResourceGroups(); this.populateResourceGroups();
} }
private async populateResourceGroups(): Promise<void> { private async populateResourceGroups(): Promise<void> {
this.migrationControllerResourceGroupDropdown.loading = true; this.migrationServiceResourceGroupDropdown.loading = true;
let subscription = this.migrationStateModel._targetSubscription; let subscription = this.migrationStateModel._targetSubscription;
const resourceGroups = await getResourceGroups(this.migrationStateModel._azureAccount, subscription); const resourceGroups = await getResourceGroups(this.migrationStateModel._azureAccount, subscription);
let resourceGroupDropdownValues: azdata.CategoryValue[] = []; let resourceGroupDropdownValues: azdata.CategoryValue[] = [];
@@ -258,39 +258,39 @@ export class CreateMigrationControllerDialog {
} }
]; ];
} }
this.migrationControllerResourceGroupDropdown.values = resourceGroupDropdownValues; this.migrationServiceResourceGroupDropdown.values = resourceGroupDropdownValues;
this.migrationControllerResourceGroupDropdown.loading = false; this.migrationServiceResourceGroupDropdown.loading = false;
} }
private createControllerStatus(): azdata.FlexContainer { private createServiceStatus(): azdata.FlexContainer {
const setupIRHeadingText = this._view.modelBuilder.text().withProps({ const setupIRHeadingText = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_DIALOG_CONTROLLER_CONTAINER_HEADING, value: constants.SERVICE_CONTAINER_HEADING,
CSSStyles: { CSSStyles: {
'font-weight': 'bold' 'font-weight': 'bold'
} }
}).component(); }).component();
const setupIRdescription = this._view.modelBuilder.text().withProps({ const setupIRdescription = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_DIALOG_CONTROLLER_CONTAINER_DESCRIPTION, value: constants.SERVICE_CONTAINER_DESCRIPTION,
}).component(); }).component();
const irSetupStep1Text = this._view.modelBuilder.text().withProps({ const irSetupStep1Text = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_STEP1, value: constants.SERVICE_STEP1,
links: [ links: [
{ {
text: constants.CONTROLLER_STEP1_LINK, text: constants.SERVICE_STEP1_LINK,
url: 'https://www.microsoft.com/download/details.aspx?id=39717' url: 'https://www.microsoft.com/download/details.aspx?id=39717'
} }
] ]
}).component(); }).component();
const irSetupStep2Text = this._view.modelBuilder.text().withProps({ const irSetupStep2Text = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_STEP2 value: constants.SERVICE_STEP2
}).component(); }).component();
const irSetupStep3Text = this._view.modelBuilder.hyperlink().withProps({ const irSetupStep3Text = this._view.modelBuilder.hyperlink().withProps({
label: constants.CONTROLLER_STEP3, label: constants.SERVICE_STEP3,
url: '', url: '',
CSSStyles: { CSSStyles: {
'margin-top': '10px', 'margin-top': '10px',
@@ -326,7 +326,7 @@ export class CreateMigrationControllerDialog {
}).component(); }).component();
this.migrationControllerAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({ this.migrationServiceAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({
columns: [ columns: [
{ {
displayName: constants.NAME, displayName: constants.NAME,
@@ -370,7 +370,7 @@ export class CreateMigrationControllerDialog {
setupIRdescription, setupIRdescription,
irSetupStep1Text, irSetupStep1Text,
irSetupStep2Text, irSetupStep2Text,
this.migrationControllerAuthKeyTable, this.migrationServiceAuthKeyTable,
irSetupStep3Text, irSetupStep3Text,
this._connectionStatus, this._connectionStatus,
refreshLoadingIndicator refreshLoadingIndicator
@@ -389,26 +389,26 @@ export class CreateMigrationControllerDialog {
private async refreshStatus(): Promise<void> { private async refreshStatus(): Promise<void> {
const subscription = this.migrationStateModel._targetSubscription; const subscription = this.migrationStateModel._targetSubscription;
const resourceGroup = (this.migrationControllerResourceGroupDropdown.value as azdata.CategoryValue).name; const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name;
const region = (this.migrationControllerRegionDropdown.value as azdata.CategoryValue).name; const region = (this.migrationServiceRegionDropdown.value as azdata.CategoryValue).name;
const controllerStatus = await getMigrationController(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.migrationStateModel._migrationController!.name); const migrationServiceStatus = await getSqlMigrationService(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.createdMigrationService!.name);
const controllerMonitoringStatus = await getMigrationControllerMonitoringData(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.migrationStateModel._migrationController!.name); const migrationServiceMonitoringStatus = await getSqlMigrationServiceMonitoringData(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.createdMigrationService!.name);
this.migrationStateModel._nodeNames = controllerMonitoringStatus.nodes.map((node) => { this.createdMigrationServiceNodeNames = migrationServiceMonitoringStatus.nodes.map((node) => {
return node.nodeName; return node.nodeName;
}); });
if (controllerStatus) { if (migrationServiceStatus) {
const state = controllerStatus.properties.integrationRuntimeState; const state = migrationServiceStatus.properties.integrationRuntimeState;
if (state === 'Online') { if (state === 'Online') {
this._connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{ this._connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.CONTROLLER_READY(this.migrationStateModel._migrationController!.name, this.migrationStateModel._nodeNames.join(', ')), text: constants.SERVICE_READY(this.createdMigrationService!.name, this.createdMigrationServiceNodeNames.join(', ')),
style: 'success' style: 'success'
}); });
this._dialogObject.okButton.enabled = true; this._dialogObject.okButton.enabled = true;
} else { } else {
this._connectionStatus.text = constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name); this._connectionStatus.text = constants.SERVICE_NOT_READY(this.createdMigrationService!.name);
this._connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{ this._connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name), text: constants.SERVICE_NOT_READY(this.createdMigrationService!.name),
style: 'warning' style: 'warning'
}); });
this._dialogObject.okButton.enabled = false; this._dialogObject.okButton.enabled = false;
@@ -418,17 +418,17 @@ export class CreateMigrationControllerDialog {
} }
private async refreshAuthTable(): Promise<void> { private async refreshAuthTable(): Promise<void> {
const subscription = this.migrationStateModel._targetSubscription; const subscription = this.migrationStateModel._targetSubscription;
const resourceGroup = (this.migrationControllerResourceGroupDropdown.value as azdata.CategoryValue).name; const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name;
const region = (this.migrationControllerRegionDropdown.value as azdata.CategoryValue).name; const region = (this.migrationServiceRegionDropdown.value as azdata.CategoryValue).name;
const keys = await getMigrationControllerAuthKeys(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.migrationStateModel._migrationController!.name); const keys = await getSqlMigrationServiceAuthKeys(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.createdMigrationService!.name);
this._copyKey1Button = this._view.modelBuilder.button().withProps({ this._copyKey1Button = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.copy iconPath: IconPathHelper.copy
}).component(); }).component();
this._copyKey1Button.onDidClick((e) => { this._copyKey1Button.onDidClick((e) => {
vscode.env.clipboard.writeText(<string>this.migrationControllerAuthKeyTable.dataValues![0][1].value); vscode.env.clipboard.writeText(<string>this.migrationServiceAuthKeyTable.dataValues![0][1].value);
vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); vscode.window.showInformationMessage(constants.SERVICE_KEY_COPIED_HELP);
}); });
this._copyKey2Button = this._view.modelBuilder.button().withProps({ this._copyKey2Button = this._view.modelBuilder.button().withProps({
@@ -436,8 +436,8 @@ export class CreateMigrationControllerDialog {
}).component(); }).component();
this._copyKey2Button.onDidClick((e) => { this._copyKey2Button.onDidClick((e) => {
vscode.env.clipboard.writeText(<string>this.migrationControllerAuthKeyTable.dataValues![1][1].value); vscode.env.clipboard.writeText(<string>this.migrationServiceAuthKeyTable.dataValues![1][1].value);
vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); vscode.window.showInformationMessage(constants.SERVICE_KEY_COPIED_HELP);
}); });
this._refreshKey1Button = this._view.modelBuilder.button().withProps({ this._refreshKey1Button = this._view.modelBuilder.button().withProps({
@@ -454,11 +454,11 @@ export class CreateMigrationControllerDialog {
this._refreshKey2Button.onDidClick((e) => { //TODO: add refresh logic this._refreshKey2Button.onDidClick((e) => { //TODO: add refresh logic
}); });
this.migrationControllerAuthKeyTable.updateProperties({ this.migrationServiceAuthKeyTable.updateProperties({
dataValues: [ dataValues: [
[ [
{ {
value: constants.CONTROLLER_KEY1_LABEL value: constants.SERVICE_KEY1_LABEL
}, },
{ {
value: keys.authKey1 value: keys.authKey1
@@ -472,7 +472,7 @@ export class CreateMigrationControllerDialog {
], ],
[ [
{ {
value: constants.CONTROLLER_KEY2_LABEL value: constants.SERVICE_KEY2_LABEL
}, },
{ {
value: keys.authKey2 value: keys.authKey2

View File

@@ -8,15 +8,17 @@ import { IconPathHelper } from '../../constants/iconPathHelper';
import { MigrationContext } from '../../models/migrationLocalStorage'; import { MigrationContext } from '../../models/migrationLocalStorage';
import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel'; import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
import * as loc from '../../constants/strings'; import * as loc from '../../constants/strings';
import { getSqlServerName } from '../../api/utils';
export class MigrationCutoverDialog { export class MigrationCutoverDialog {
private _dialogObject!: azdata.window.Dialog; private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView; private _view!: azdata.ModelView;
private _model: MigrationCutoverDialogModel; private _model: MigrationCutoverDialogModel;
private _databaseTitleName!: azdata.TextComponent; private _databaseTitleName!: azdata.TextComponent;
private _databaseCutoverButton!: azdata.ButtonComponent; private _cutoverButton!: azdata.ButtonComponent;
private _refresh!: azdata.ButtonComponent; private _refreshButton!: azdata.ButtonComponent;
private _cancel!: azdata.ButtonComponent; private _cancelButton!: azdata.ButtonComponent;
private _refreshLoader!: azdata.LoadingComponent;
private _serverName!: azdata.TextComponent; private _serverName!: azdata.TextComponent;
private _serverVersion!: azdata.TextComponent; private _serverVersion!: azdata.TextComponent;
@@ -43,7 +45,7 @@ export class MigrationCutoverDialog {
let tab = azdata.window.createTab(''); let tab = azdata.window.createTab('');
tab.registerContent(async (view: azdata.ModelView) => { tab.registerContent(async (view: azdata.ModelView) => {
this._view = view; this._view = view;
const sourceDetails = this.createInfoField(loc.SOURCE_VERSION, ''); const sourceDetails = this.createInfoField(loc.SOURCE_SERVER, '');
const sourceVersion = this.createInfoField(loc.SOURCE_VERSION, ''); const sourceVersion = this.createInfoField(loc.SOURCE_VERSION, '');
this._serverName = sourceDetails.text; this._serverName = sourceDetails.text;
@@ -183,30 +185,30 @@ export class MigrationCutoverDialog {
columns: [ columns: [
{ {
value: loc.ACTIVE_BACKUP_FILES, value: loc.ACTIVE_BACKUP_FILES,
width: 150, width: 280,
type: azdata.ColumnType.text type: azdata.ColumnType.text
}, },
{ {
value: loc.TYPE, value: loc.TYPE,
width: 100, width: 90,
type: azdata.ColumnType.text type: azdata.ColumnType.text
}, },
{ {
value: loc.STATUS, value: loc.STATUS,
width: 100, width: 60,
type: azdata.ColumnType.text type: azdata.ColumnType.text
}, },
{ {
value: loc.BACKUP_START_TIME, value: loc.BACKUP_START_TIME,
width: 150, width: 130,
type: azdata.ColumnType.text type: azdata.ColumnType.text
}, { }, {
value: loc.FIRST_LSN, value: loc.FIRST_LSN,
width: 150, width: 120,
type: azdata.ColumnType.text type: azdata.ColumnType.text
}, { }, {
value: loc.LAST_LSN, value: loc.LAST_LSN,
width: 150, width: 120,
type: azdata.ColumnType.text type: azdata.ColumnType.text
} }
], ],
@@ -235,11 +237,12 @@ export class MigrationCutoverDialog {
} }
); );
const form = formBuilder.withLayout({ width: '100%' }).component(); const form = formBuilder.withLayout({ width: '100%' }).component();
return view.initializeModel(form); return view.initializeModel(form).then((value) => {
this.refreshStatus();
});
}); });
this._dialogObject.content = [tab]; this._dialogObject.content = [tab];
azdata.window.openDialog(this._dialogObject); azdata.window.openDialog(this._dialogObject);
this.refreshStatus();
} }
@@ -262,7 +265,7 @@ export class MigrationCutoverDialog {
} }
}); });
this._databaseCutoverButton = this._view.modelBuilder.button().withProps({ this._cutoverButton = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.cutover, iconPath: IconPathHelper.cutover,
iconHeight: '14px', iconHeight: '14px',
iconWidth: '12px', iconWidth: '12px',
@@ -272,7 +275,7 @@ export class MigrationCutoverDialog {
enabled: false enabled: false
}).component(); }).component();
this._databaseCutoverButton.onDidClick(async (e) => { this._cutoverButton.onDidClick(async (e) => {
if (this._startCutover) { if (this._startCutover) {
await this._model.startCutover(); await this._model.startCutover();
this.refreshStatus(); this.refreshStatus();
@@ -284,14 +287,14 @@ export class MigrationCutoverDialog {
} }
}); });
header.addItem(this._databaseCutoverButton, { header.addItem(this._cutoverButton, {
flex: '0', flex: '0',
CSSStyles: { CSSStyles: {
'width': '100px' 'width': '100px'
} }
}); });
this._cancel = this._view.modelBuilder.button().withProps({ this._cancelButton = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.discard, iconPath: IconPathHelper.discard,
iconHeight: '16px', iconHeight: '16px',
iconWidth: '16px', iconWidth: '16px',
@@ -300,11 +303,11 @@ export class MigrationCutoverDialog {
width: '130px' width: '130px'
}).component(); }).component();
this._cancel.onDidClick((e) => { this._cancelButton.onDidClick((e) => {
this.cancelMigration(); this.cancelMigration();
}); });
header.addItem(this._cancel, { header.addItem(this._cancelButton, {
flex: '0', flex: '0',
CSSStyles: { CSSStyles: {
'width': '130px' 'width': '130px'
@@ -312,7 +315,7 @@ export class MigrationCutoverDialog {
}); });
this._refresh = this._view.modelBuilder.button().withProps({ this._refreshButton = this._view.modelBuilder.button().withProps({
iconPath: IconPathHelper.refresh, iconPath: IconPathHelper.refresh,
iconHeight: '16px', iconHeight: '16px',
iconWidth: '16px', iconWidth: '16px',
@@ -321,28 +324,39 @@ export class MigrationCutoverDialog {
width: '100px' width: '100px'
}).component(); }).component();
this._refresh.onDidClick((e) => { this._refreshButton.onDidClick((e) => {
this.refreshStatus(); this.refreshStatus();
}); });
header.addItem(this._refresh, { header.addItem(this._refreshButton, {
flex: '0', flex: '0',
CSSStyles: { CSSStyles: {
'width': '100px' 'width': '100px'
} }
}); });
this._refreshLoader = this._view.modelBuilder.loadingComponent().withProps({
loading: false,
height: '55px'
}).component();
header.addItem(this._refreshLoader, {
flex: '0'
});
return header; return header;
} }
private async refreshStatus(): Promise<void> { private async refreshStatus(): Promise<void> {
try { try {
this._refreshLoader.loading = true;
this._cutoverButton.enabled = false;
this._cancelButton.enabled = false;
await this._model.fetchStatus(); await this._model.fetchStatus();
const sqlServerInfo = await azdata.connection.getServerInfo(this._model._migration.sourceConnectionProfile.connectionId); const sqlServerInfo = await azdata.connection.getServerInfo(this._model._migration.sourceConnectionProfile.connectionId);
const sqlServerName = this._model._migration.sourceConnectionProfile.serverName; const sqlServerName = this._model._migration.sourceConnectionProfile.serverName;
const sqlServerVersion = sqlServerInfo.serverVersion; const versionName = getSqlServerName(sqlServerInfo.serverMajorVersion!);
const sqlServerEdition = sqlServerInfo.serverEdition; const sqlServerVersion = versionName ? versionName : sqlServerInfo.serverVersion;
const targetServerName = this._model._migration.targetManagedInstance.name; const targetServerName = this._model._migration.targetManagedInstance.name;
let targetServerVersion; let targetServerVersion;
if (this._model.migrationStatus.id.includes('managedInstances')) { if (this._model.migrationStatus.id.includes('managedInstances')) {
@@ -382,7 +396,7 @@ export class MigrationCutoverDialog {
this._serverName.value = sqlServerName; this._serverName.value = sqlServerName;
this._serverVersion.value = `${sqlServerVersion} this._serverVersion.value = `${sqlServerVersion}
${sqlServerEdition}`; ${sqlServerInfo.serverVersion}`;
this._targetServer.value = targetServerName; this._targetServer.value = targetServerName;
this._targetVersion.value = targetServerVersion; this._targetVersion.value = targetServerVersion;
@@ -396,6 +410,9 @@ export class MigrationCutoverDialog {
this._fileCount.value = loc.ACTIVE_BACKUP_FILES_ITEMS(tableData.length); this._fileCount.value = loc.ACTIVE_BACKUP_FILES_ITEMS(tableData.length);
//Sorting files in descending order of backupStartTime
tableData.sort((file1, file2) => new Date(file1.backupStartTime) > new Date(file2.backupStartTime) ? - 1 : 1);
this.fileTable.data = tableData.map((row) => { this.fileTable.data = tableData.map((row) => {
return [ return [
row.fileName, row.fileName,
@@ -411,14 +428,17 @@ export class MigrationCutoverDialog {
} }
if (migrationStatusTextValue === 'InProgress') { if (migrationStatusTextValue === 'InProgress') {
this._databaseCutoverButton.enabled = true; const fileNotRestored = await tableData.some(file => file.status !== 'Restored');
this._cutoverButton.enabled = !fileNotRestored;
this._cancelButton.enabled = true;
} else { } else {
this._databaseCutoverButton.enabled = false; this._cutoverButton.enabled = false;
this._cancel.enabled = false; this._cancelButton.enabled = false;
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
this._refreshLoader.loading = false;
} }
private createInfoField(label: string, value: string): { private createInfoField(label: string, value: string): {

View File

@@ -19,6 +19,7 @@ export class MigrationStatusDialog {
private _refresh!: azdata.ButtonComponent; private _refresh!: azdata.ButtonComponent;
private _statusDropdown!: azdata.DropDownComponent; private _statusDropdown!: azdata.DropDownComponent;
private _statusTable!: azdata.DeclarativeTableComponent; private _statusTable!: azdata.DeclarativeTableComponent;
private _refreshLoader!: azdata.LoadingComponent;
constructor(migrations: MigrationContext[], private _filter: MigrationCategory) { constructor(migrations: MigrationContext[], private _filter: MigrationCategory) {
this._model = new MigrationStatusDialogModel(migrations); this._model = new MigrationStatusDialogModel(migrations);
@@ -102,11 +103,19 @@ export class MigrationStatusDialog {
} }
}); });
this._refreshLoader = this._view.modelBuilder.loadingComponent().withProps({
loading: false,
height: '55px'
}).component();
flexContainer.addItem(this._refreshLoader, {
flex: '0'
});
return flexContainer; return flexContainer;
} }
private populateMigrationTable(): void { private populateMigrationTable(): void {
try { try {
const migrations = this._model.filterMigration( const migrations = this._model.filterMigration(
this._searchBox.value!, this._searchBox.value!,
@@ -115,6 +124,10 @@ export class MigrationStatusDialog {
const data: azdata.DeclarativeTableCellValue[][] = []; const data: azdata.DeclarativeTableCellValue[][] = [];
migrations.sort((m1, m2) => {
return new Date(m1.migrationContext.properties.startedOn) > new Date(m2.migrationContext.properties.startedOn) ? -1 : 1;
});
migrations.forEach((migration) => { migrations.forEach((migration) => {
const migrationRow: azdata.DeclarativeTableCellValue[] = []; const migrationRow: azdata.DeclarativeTableCellValue[] = [];
@@ -133,8 +146,8 @@ export class MigrationStatusDialog {
value: migration.migrationContext.properties.migrationStatus value: migration.migrationContext.properties.migrationStatus
}); });
const sqlMigrationIcon = this._view.modelBuilder.image().withProps({ const targetMigrationIcon = this._view.modelBuilder.image().withProps({
iconPath: IconPathHelper.sqlMigrationLogo, iconPath: (migration.targetManagedInstance.type === 'microsoft.sql/managedinstances') ? IconPathHelper.sqlMiLogo : IconPathHelper.sqlVmLogo,
iconWidth: '16px', iconWidth: '16px',
iconHeight: '16px', iconHeight: '16px',
width: '32px', width: '32px',
@@ -145,7 +158,7 @@ export class MigrationStatusDialog {
url: '' url: ''
}).component(); }).component();
sqlMigrationName.onDidClick((e) => { sqlMigrationName.onDidClick((e) => {
vscode.window.showInformationMessage('Feature coming soon'); vscode.window.showInformationMessage(loc.COMING_SOON);
}); });
const sqlMigrationContainer = this._view.modelBuilder.flexContainer().withProps({ const sqlMigrationContainer = this._view.modelBuilder.flexContainer().withProps({
@@ -153,7 +166,7 @@ export class MigrationStatusDialog {
'justify-content': 'center' 'justify-content': 'center'
} }
}).component(); }).component();
sqlMigrationContainer.addItem(sqlMigrationIcon, { sqlMigrationContainer.addItem(targetMigrationIcon, {
flex: '0', flex: '0',
CSSStyles: { CSSStyles: {
'width': '32px' 'width': '32px'
@@ -174,10 +187,10 @@ export class MigrationStatusDialog {
}); });
migrationRow.push({ migrationRow.push({
value: '---' value: (migration.migrationContext.properties.startedOn) ? new Date(migration.migrationContext.properties.startedOn).toLocaleString() : '---'
}); });
migrationRow.push({ migrationRow.push({
value: '---' value: (migration.migrationContext.properties.endedOn) ? new Date(migration.migrationContext.properties.endedOn).toLocaleString() : '---'
}); });
data.push(migrationRow); data.push(migrationRow);
@@ -190,6 +203,7 @@ export class MigrationStatusDialog {
} }
private refreshTable(): void { private refreshTable(): void {
this._refreshLoader.loading = true;
this._model._migrations.forEach(async (migration) => { this._model._migrations.forEach(async (migration) => {
migration.migrationContext = await getDatabaseMigration( migration.migrationContext = await getDatabaseMigration(
migration.azureAccount, migration.azureAccount,
@@ -198,8 +212,8 @@ export class MigrationStatusDialog {
migration.migrationContext.id migration.migrationContext.id
); );
}); });
this.populateMigrationTable(); this.populateMigrationTable();
this._refreshLoader.loading = false;
} }
private createStatusTable(): azdata.DeclarativeTableComponent { private createStatusTable(): azdata.DeclarativeTableComponent {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { azureResource } from 'azureResource'; import { azureResource } from 'azureResource';
import { DatabaseMigration, SqlMigrationController, SqlManagedInstance } from '../api/azure'; import { DatabaseMigration, SqlMigrationService, SqlManagedInstance, getDatabaseMigration } from '../api/azure';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
@@ -16,23 +16,39 @@ export class MigrationLocalStorage {
MigrationLocalStorage.context = context; MigrationLocalStorage.context = context;
} }
public static getMigrationsBySourceConnections(connectionProfile: azdata.connection.ConnectionProfile): MigrationContext[] { public static async getMigrationsBySourceConnections(connectionProfile: azdata.connection.ConnectionProfile, refreshStatus?: boolean): Promise<MigrationContext[]> {
let dataBaseMigrations: MigrationContext[] = []; const result: MigrationContext[] = [];
try { const validMigrations: MigrationContext[] = [];
const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
dataBaseMigrations = migrationMementos.filter((memento) => { const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
return memento.sourceConnectionProfile.serverName === connectionProfile.serverName; for (let i = 0; i < migrationMementos.length; i++) {
}).map((memento) => { const migration = migrationMementos[i];
return memento; if (migration.sourceConnectionProfile.serverName === connectionProfile.serverName) {
}); if (refreshStatus) {
} catch (e) { try {
console.log(e); migration.migrationContext = await getDatabaseMigration(
migration.azureAccount,
migration.subscription,
migration.targetManagedInstance.location,
migration.migrationContext.id
);
}
catch (e) {
// Keeping only valid migrations in cache. Clearing all the migrations which return ResourceDoesNotExit error.
if (e.message === 'ResourceDoesNotExist') {
continue;
} else {
console.log(e);
}
}
}
result.push(migration);
}
validMigrations.push(migration);
} }
this.context.globalState.update(this.mementoToken, validMigrations);
return result;
return dataBaseMigrations;
} }
public static saveMigration( public static saveMigration(
@@ -41,7 +57,7 @@ export class MigrationLocalStorage {
targetMI: SqlManagedInstance, targetMI: SqlManagedInstance,
azureAccount: azdata.Account, azureAccount: azdata.Account,
subscription: azureResource.AzureResourceSubscription, subscription: azureResource.AzureResourceSubscription,
controller: SqlMigrationController): void { controller: SqlMigrationService): void {
try { try {
const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || []; const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
migrationMementos.push({ migrationMementos.push({
@@ -69,5 +85,5 @@ export interface MigrationContext {
targetManagedInstance: SqlManagedInstance, targetManagedInstance: SqlManagedInstance,
azureAccount: azdata.Account, azureAccount: azdata.Account,
subscription: azureResource.AzureResourceSubscription, subscription: azureResource.AzureResourceSubscription,
controller: SqlMigrationController controller: SqlMigrationService
} }

View File

@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IconPath } from 'azdata';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -23,11 +24,11 @@ export interface Checks {
export interface Product extends MigrationProduct { export interface Product extends MigrationProduct {
readonly name: string; readonly name: string;
readonly learnMoreLink?: string; readonly learnMoreLink?: string;
readonly icon?: string; readonly icon?: IconPath;
} }
export class Product implements Product { export class Product implements Product {
constructor(public readonly type: MigrationProductType, public readonly name: string, public readonly icon?: string, public readonly learnMoreLink?: string) { constructor(public readonly type: MigrationProductType, public readonly name: string, public readonly icon?: IconPath, public readonly learnMoreLink?: string) {
} }

View File

@@ -8,7 +8,7 @@ import { azureResource } from 'azureResource';
import * as azurecore from 'azurecore'; import * as azurecore from 'azurecore';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as mssql from '../../../mssql'; import * as mssql from '../../../mssql';
import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getMigrationControllers, getSubscriptions, SqlMigrationController, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, getAvailableSqlVMs, SqlVMServer } from '../api/azure'; import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getSqlMigrationServices, getSubscriptions, SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, getAvailableSqlVMs, SqlVMServer } from '../api/azure';
import { SKURecommendations } from './externalContract'; import { SKURecommendations } from './externalContract';
import * as constants from '../constants/strings'; import * as constants from '../constants/strings';
import { MigrationLocalStorage } from './migrationLocalStorage'; import { MigrationLocalStorage } from './migrationLocalStorage';
@@ -96,8 +96,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _refreshNetworkShareLocation!: azureResource.BlobContainer[]; public _refreshNetworkShareLocation!: azureResource.BlobContainer[];
public _targetDatabaseNames!: string[]; public _targetDatabaseNames!: string[];
public _migrationController!: SqlMigrationController; public _sqlMigrationService!: SqlMigrationService;
public _migrationControllers!: SqlMigrationController[]; public _sqlMigrationServices!: SqlMigrationService[];
public _nodeNames!: string[]; public _nodeNames!: string[];
private _stateChangeEventEmitter = new vscode.EventEmitter<StateChangeEvent>(); private _stateChangeEventEmitter = new vscode.EventEmitter<StateChangeEvent>();
@@ -426,39 +426,39 @@ export class MigrationStateModel implements Model, vscode.Disposable {
} }
public async getMigrationControllerValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance): Promise<azdata.CategoryValue[]> { public async getSqlMigrationServiceValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance): Promise<azdata.CategoryValue[]> {
let migrationControllerValues: azdata.CategoryValue[] = []; let sqlMigrationServiceValues: azdata.CategoryValue[] = [];
try { try {
this._migrationControllers = await getMigrationControllers(this._azureAccount, subscription, managedInstance.resourceGroup!, managedInstance.location); this._sqlMigrationServices = await getSqlMigrationServices(this._azureAccount, subscription, managedInstance.location);
this._migrationControllers.forEach((migrationController) => { this._sqlMigrationServices.forEach((sqlMigrationService) => {
migrationControllerValues.push({ sqlMigrationServiceValues.push({
name: migrationController.id, name: sqlMigrationService.id,
displayName: `${migrationController.name}` displayName: `${sqlMigrationService.name}`
}); });
}); });
if (migrationControllerValues.length === 0) { if (sqlMigrationServiceValues.length === 0) {
migrationControllerValues = [ sqlMigrationServiceValues = [
{ {
displayName: constants.MIGRATION_CONTROLLER_NOT_FOUND_ERROR, displayName: constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR,
name: '' name: ''
} }
]; ];
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
migrationControllerValues = [ sqlMigrationServiceValues = [
{ {
displayName: constants.MIGRATION_CONTROLLER_NOT_FOUND_ERROR, displayName: constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR,
name: '' name: ''
} }
]; ];
} }
return migrationControllerValues; return sqlMigrationServiceValues;
} }
public getMigrationController(index: number): SqlMigrationController { public getMigrationService(index: number): SqlMigrationService {
return this._migrationControllers[index]; return this._sqlMigrationServices[index];
} }
public async startMigration() { public async startMigration() {
@@ -473,41 +473,41 @@ export class MigrationStateModel implements Model, vscode.Disposable {
const connectionPassword = await azdata.connection.getCredentials(this.sourceConnectionId); const connectionPassword = await azdata.connection.getCredentials(this.sourceConnectionId);
const requestBody: StartDatabaseMigrationRequest = { const requestBody: StartDatabaseMigrationRequest = {
location: this._migrationController?.properties.location!, location: this._sqlMigrationService?.properties.location!,
properties: { properties: {
SourceDatabaseName: '', sourceDatabaseName: '',
MigrationController: this._migrationController?.id!, migrationService: this._sqlMigrationService?.id!,
BackupConfiguration: { backupConfiguration: {
TargetLocation: { targetLocation: {
StorageAccountResourceId: this._databaseBackup.storageAccount.id, storageAccountResourceId: this._databaseBackup.storageAccount.id,
AccountKey: this._databaseBackup.storageKey, accountKey: this._databaseBackup.storageKey,
}, },
SourceLocation: { sourceLocation: {
FileShare: { fileShare: {
Path: '', path: '',
Username: this._databaseBackup.windowsUser, username: this._databaseBackup.windowsUser,
Password: this._databaseBackup.password, password: this._databaseBackup.password,
} }
}, },
}, },
SourceSqlConnection: { sourceSqlConnection: {
DataSource: currentConnection?.serverName!, dataSource: currentConnection?.serverName!,
Username: currentConnection?.userName!, username: currentConnection?.userName!,
Password: connectionPassword.password password: connectionPassword.password
}, },
Scope: this._targetServerInstance.id scope: this._targetServerInstance.id
} }
}; };
this._migrationDbs.forEach(async (db, index) => { this._migrationDbs.forEach(async (db, index) => {
requestBody.properties.SourceDatabaseName = db; requestBody.properties.sourceDatabaseName = db;
try { try {
requestBody.properties.BackupConfiguration.SourceLocation.FileShare.Path = this._databaseBackup.networkShareLocations[index]; requestBody.properties.backupConfiguration.sourceLocation.fileShare!.path = this._databaseBackup.networkShareLocations[index];
const response = await startDatabaseMigration( const response = await startDatabaseMigration(
this._azureAccount, this._azureAccount,
this._targetSubscription, this._targetSubscription,
this._migrationController?.properties.location!, this._sqlMigrationService?.properties.location!,
this._targetServerInstance, this._targetServerInstance,
this._targetDatabaseNames[index], this._targetDatabaseNames[index],
requestBody requestBody
@@ -519,9 +519,9 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._targetServerInstance, this._targetServerInstance,
this._azureAccount, this._azureAccount,
this._targetSubscription, this._targetSubscription,
this._migrationController this._sqlMigrationService
); );
vscode.window.showInformationMessage(localize("sql.migration.starting.migration.message", 'Starting migration for database {0} to {1}', db, this._targetServerInstance.name)); vscode.window.showInformationMessage(localize("sql.migration.starting.migration.message", 'Starting migration for database {0} to {1} - {2}', db, this._targetServerInstance.name, this._targetDatabaseNames[index]));
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);

View File

@@ -47,6 +47,15 @@ export class AccountsSelectionPage extends MigrationWizardPage {
}; };
return false; return false;
} }
if (this.migrationStateModel._azureAccount?.isStale) {
this.wizard.message = {
text: constants.ACCOUNT_STALE_ERROR(this.migrationStateModel._azureAccount)
};
return false;
}
this.wizard.message = {
text: ''
};
return true; return true;
}).component(); }).component();
@@ -69,7 +78,7 @@ export class AccountsSelectionPage extends MigrationWizardPage {
this.migrationStateModel._subscriptions = undefined!; this.migrationStateModel._subscriptions = undefined!;
this.migrationStateModel._targetSubscription = undefined!; this.migrationStateModel._targetSubscription = undefined!;
this.migrationStateModel._databaseBackup.subscription = undefined!; this.migrationStateModel._databaseBackup.subscription = undefined!;
this._azureAccountsDropdown.validate();
} }
}); });
@@ -83,6 +92,9 @@ export class AccountsSelectionPage extends MigrationWizardPage {
linkAccountButton.onDidClick(async (event) => { linkAccountButton.onDidClick(async (event) => {
await vscode.commands.executeCommand('workbench.actions.modal.linkedAccount'); await vscode.commands.executeCommand('workbench.actions.modal.linkedAccount');
await this.populateAzureAccountsDropdown(); await this.populateAzureAccountsDropdown();
this.wizard.message = {
text: ''
};
}); });
const flexContainer = view.modelBuilder.flexContainer() const flexContainer = view.modelBuilder.flexContainer()
@@ -155,6 +167,15 @@ export class AccountsSelectionPage extends MigrationWizardPage {
} }
public async onPageEnter(): Promise<void> { public async onPageEnter(): Promise<void> {
this.wizard.registerNavigationValidator(pageChangeInfo => {
if (this.migrationStateModel._azureAccount.isStale === true) {
this.wizard.message = {
text: constants.ACCOUNT_STALE_ERROR(this.migrationStateModel._azureAccount)
};
return false;
}
return true;
});
} }
public async onPageLeave(): Promise<void> { public async onPageLeave(): Promise<void> {

View File

@@ -7,15 +7,15 @@ 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 { MigrationStateModel, StateChangeEvent } from '../models/stateMachine'; import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import { CreateMigrationControllerDialog } from '../dialog/createMigrationDialog/createMigrationControllerDialog'; import { CreateSqlMigrationServiceDialog } from '../dialog/createSqlMigrationService/createSqlMigrationServiceDialog';
import * as constants from '../constants/strings'; import * as constants from '../constants/strings';
import { createInformationRow, WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; import { createInformationRow, WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
import { getMigrationController, getMigrationControllerAuthKeys, getMigrationControllerMonitoringData } from '../api/azure'; import { getSqlMigrationService, getSqlMigrationServiceAuthKeys, getSqlMigrationServiceMonitoringData, SqlMigrationService } from '../api/azure';
import { IconPathHelper } from '../constants/iconPathHelper'; import { IconPathHelper } from '../constants/iconPathHelper';
export class IntergrationRuntimePage extends MigrationWizardPage { export class IntergrationRuntimePage extends MigrationWizardPage {
private migrationControllerDropdown!: azdata.DropDownComponent; private migrationServiceDropdown!: azdata.DropDownComponent;
private _view!: azdata.ModelView; private _view!: azdata.ModelView;
private _form!: azdata.FormBuilder; private _form!: azdata.FormBuilder;
private _statusLoadingComponent!: azdata.LoadingComponent; private _statusLoadingComponent!: azdata.LoadingComponent;
@@ -28,13 +28,13 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
protected async registerContent(view: azdata.ModelView): Promise<void> { protected async registerContent(view: azdata.ModelView): Promise<void> {
this._view = view; this._view = view;
const createNewController = view.modelBuilder.hyperlink().withProps({ const createNewMigrationService = view.modelBuilder.hyperlink().withProps({
label: constants.CREATE_NEW, label: constants.CREATE_NEW,
url: '' url: ''
}).component(); }).component();
createNewController.onDidClick((e) => { createNewMigrationService.onDidClick((e) => {
const dialog = new CreateMigrationControllerDialog(this.migrationStateModel, this); const dialog = new CreateSqlMigrationServiceDialog(this.migrationStateModel, this);
dialog.initialize(); dialog.initialize();
}); });
@@ -47,10 +47,10 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
.withFormItems( .withFormItems(
[ [
{ {
component: this.migrationControllerDropdownsContainer() component: this.migrationServiceDropdownContainer()
}, },
{ {
component: createNewController component: createNewMigrationService
}, },
{ {
component: this._statusLoadingComponent component: this._statusLoadingComponent
@@ -62,7 +62,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
} }
public async onPageEnter(): Promise<void> { public async onPageEnter(): Promise<void> {
this.populateMigrationController(); this.populateMigrationService();
this.wizard.registerNavigationValidator((pageChangeInfo) => { this.wizard.registerNavigationValidator((pageChangeInfo) => {
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) { if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
this.wizard.message = { this.wizard.message = {
@@ -70,18 +70,18 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}; };
return true; return true;
} }
const state = this.migrationStateModel._migrationController.properties.integrationRuntimeState; const state = this.migrationStateModel._sqlMigrationService.properties.integrationRuntimeState;
if (!this.migrationStateModel._migrationController) { if (!this.migrationStateModel._sqlMigrationService) {
this.wizard.message = { this.wizard.message = {
level: azdata.window.MessageLevel.Error, level: azdata.window.MessageLevel.Error,
text: constants.INVALID_CONTROLLER_ERROR text: constants.INVALID_SERVICE_ERROR
}; };
return false; return false;
} }
if (state !== 'Online') { if (state !== 'Online') {
this.wizard.message = { this.wizard.message = {
level: azdata.window.MessageLevel.Error, level: azdata.window.MessageLevel.Error,
text: constants.CONTROLLER_OFFLINE_ERROR text: constants.SERVICE_OFFLINE_ERROR
}; };
return false; return false;
} else { } else {
@@ -102,7 +102,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
protected async handleStateChange(e: StateChangeEvent): Promise<void> { protected async handleStateChange(e: StateChangeEvent): Promise<void> {
} }
private migrationControllerDropdownsContainer(): azdata.FlexContainer { private migrationServiceDropdownContainer(): azdata.FlexContainer {
const descriptionText = this._view.modelBuilder.text().withProps({ const descriptionText = this._view.modelBuilder.text().withProps({
value: constants.IR_PAGE_DESCRIPTION, value: constants.IR_PAGE_DESCRIPTION,
links: [ links: [
@@ -117,23 +117,23 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
value: constants.IR_PAGE_NOTE value: constants.IR_PAGE_NOTE
}).component(); }).component();
const migrationControllerDropdownLabel = this._view.modelBuilder.text().withProps({ const migrationServcieDropdownLabel = this._view.modelBuilder.text().withProps({
value: constants.SELECT_A_MIGRATION_CONTROLLER value: constants.SELECT_A_SQL_MIGRATION_SERVICE
}).component(); }).component();
this.migrationControllerDropdown = this._view.modelBuilder.dropDown().withProps({ this.migrationServiceDropdown = this._view.modelBuilder.dropDown().withProps({
required: true, required: true,
width: WIZARD_INPUT_COMPONENT_WIDTH width: WIZARD_INPUT_COMPONENT_WIDTH
}).component(); }).component();
this.migrationControllerDropdown.onValueChanged(async (value) => { this.migrationServiceDropdown.onValueChanged(async (value) => {
if (value.selected) { if (value.selected) {
this.wizard.message = { this.wizard.message = {
text: '' text: ''
}; };
this.migrationStateModel._migrationController = this.migrationStateModel.getMigrationController(value.index); this.migrationStateModel._sqlMigrationService = this.migrationStateModel.getMigrationService(value.index);
if (value !== constants.MIGRATION_CONTROLLER_NOT_FOUND_ERROR) { if (value !== constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR) {
await this.loadControllerStatus(); await this.loadMigrationServiceStatus();
} }
} }
}); });
@@ -141,73 +141,77 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
const flexContainer = this._view.modelBuilder.flexContainer().withItems([ const flexContainer = this._view.modelBuilder.flexContainer().withItems([
descriptionText, descriptionText,
noteText, noteText,
migrationControllerDropdownLabel, migrationServcieDropdownLabel,
this.migrationControllerDropdown this.migrationServiceDropdown
]).withLayout({ ]).withLayout({
flexFlow: 'column' flexFlow: 'column'
}).component(); }).component();
return flexContainer; return flexContainer;
} }
public async populateMigrationController(): Promise<void> { public async populateMigrationService(sqlMigrationService?: SqlMigrationService, serviceNodes?: string[]): Promise<void> {
this.migrationControllerDropdown.loading = true; this.migrationServiceDropdown.loading = true;
if (sqlMigrationService && serviceNodes) {
this.migrationStateModel._sqlMigrationService = sqlMigrationService;
this.migrationStateModel._nodeNames = serviceNodes;
}
try { try {
this.migrationControllerDropdown.values = await this.migrationStateModel.getMigrationControllerValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._targetServerInstance); this.migrationServiceDropdown.values = await this.migrationStateModel.getSqlMigrationServiceValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._targetServerInstance);
if (this.migrationStateModel._migrationController) { if (this.migrationStateModel._sqlMigrationService) {
this.migrationControllerDropdown.value = { this.migrationServiceDropdown.value = {
name: this.migrationStateModel._migrationController.id, name: this.migrationStateModel._sqlMigrationService.id,
displayName: this.migrationStateModel._migrationController.name displayName: this.migrationStateModel._sqlMigrationService.name
}; };
} else { } else {
this.migrationStateModel._migrationController = this.migrationStateModel.getMigrationController(0); this.migrationStateModel._sqlMigrationService = this.migrationStateModel.getMigrationService(0);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
this.migrationControllerDropdown.loading = false; this.migrationServiceDropdown.loading = false;
} }
} }
private async loadControllerStatus(): Promise<void> { private async loadMigrationServiceStatus(): Promise<void> {
this._statusLoadingComponent.loading = true; this._statusLoadingComponent.loading = true;
try { try {
this._migrationDetailsContainer.clearItems(); this._migrationDetailsContainer.clearItems();
if (this.migrationStateModel._migrationController) { if (this.migrationStateModel._sqlMigrationService) {
const controller = await getMigrationController( const migrationService = await getSqlMigrationService(
this.migrationStateModel._azureAccount, this.migrationStateModel._azureAccount,
this.migrationStateModel._targetSubscription, this.migrationStateModel._targetSubscription,
this.migrationStateModel._migrationController.properties.resourceGroup, this.migrationStateModel._sqlMigrationService.properties.resourceGroup,
this.migrationStateModel._migrationController.properties.location, this.migrationStateModel._sqlMigrationService.properties.location,
this.migrationStateModel._migrationController.name); this.migrationStateModel._sqlMigrationService.name);
this.migrationStateModel._migrationController = controller; this.migrationStateModel._sqlMigrationService = migrationService;
const controllerMonitoringStatus = await getMigrationControllerMonitoringData( const migrationServiceMonitoringStatus = await getSqlMigrationServiceMonitoringData(
this.migrationStateModel._azureAccount, this.migrationStateModel._azureAccount,
this.migrationStateModel._targetSubscription, this.migrationStateModel._targetSubscription,
this.migrationStateModel._migrationController.properties.resourceGroup, this.migrationStateModel._sqlMigrationService.properties.resourceGroup,
this.migrationStateModel._migrationController.properties.location, this.migrationStateModel._sqlMigrationService.properties.location,
this.migrationStateModel._migrationController!.name); this.migrationStateModel._sqlMigrationService!.name);
this.migrationStateModel._nodeNames = controllerMonitoringStatus.nodes.map((node) => { this.migrationStateModel._nodeNames = migrationServiceMonitoringStatus.nodes.map((node) => {
return node.nodeName; return node.nodeName;
}); });
const migrationControllerAuthKeys = await getMigrationControllerAuthKeys( const migrationServiceAuthKeys = await getSqlMigrationServiceAuthKeys(
this.migrationStateModel._azureAccount, this.migrationStateModel._azureAccount,
this.migrationStateModel._targetSubscription, this.migrationStateModel._targetSubscription,
this.migrationStateModel._migrationController.properties.resourceGroup, this.migrationStateModel._sqlMigrationService.properties.resourceGroup,
this.migrationStateModel._migrationController.properties.location, this.migrationStateModel._sqlMigrationService.properties.location,
this.migrationStateModel._migrationController!.name this.migrationStateModel._sqlMigrationService!.name
); );
const migrationControllerTitle = this._view.modelBuilder.text().withProps({ const migrationServiceTitle = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_DETAILS_HEADER(controller.name), value: constants.SQL_MIGRATION_SERVICE_DETAILS_HEADER(migrationService.name),
CSSStyles: { CSSStyles: {
'font-weight': 'bold' 'font-weight': 'bold'
} }
}).component(); }).component();
const connectionStatusLabel = this._view.modelBuilder.text().withProps({ const connectionStatusLabel = this._view.modelBuilder.text().withProps({
value: constants.CONTROLLER_CONNECTION_STATUS, value: constants.SERVICE_CONNECTION_STATUS,
CSSStyles: { CSSStyles: {
'font-weight': 'bold', 'font-weight': 'bold',
'width': '150px' 'width': '150px'
@@ -242,32 +246,32 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
refreshStatus.onDidClick(async (e) => { refreshStatus.onDidClick(async (e) => {
connectionStatusLoader.loading = true; connectionStatusLoader.loading = true;
const controller = await getMigrationController( const migrationService = await getSqlMigrationService(
this.migrationStateModel._azureAccount, this.migrationStateModel._azureAccount,
this.migrationStateModel._targetSubscription, this.migrationStateModel._targetSubscription,
this.migrationStateModel._migrationController.properties.resourceGroup, this.migrationStateModel._sqlMigrationService.properties.resourceGroup,
this.migrationStateModel._migrationController.properties.location, this.migrationStateModel._sqlMigrationService.properties.location,
this.migrationStateModel._migrationController.name); this.migrationStateModel._sqlMigrationService.name);
this.migrationStateModel._migrationController = controller; this.migrationStateModel._sqlMigrationService = migrationService;
const controllerMonitoringStatus = await getMigrationControllerMonitoringData( const migrationServiceMonitoringStatus = await getSqlMigrationServiceMonitoringData(
this.migrationStateModel._azureAccount, this.migrationStateModel._azureAccount,
this.migrationStateModel._targetSubscription, this.migrationStateModel._targetSubscription,
this.migrationStateModel._migrationController.properties.resourceGroup, this.migrationStateModel._sqlMigrationService.properties.resourceGroup,
this.migrationStateModel._migrationController.properties.location, this.migrationStateModel._sqlMigrationService.properties.location,
this.migrationStateModel._migrationController!.name); this.migrationStateModel._sqlMigrationService!.name);
this.migrationStateModel._nodeNames = controllerMonitoringStatus.nodes.map((node) => { this.migrationStateModel._nodeNames = migrationServiceMonitoringStatus.nodes.map((node) => {
return node.nodeName; return node.nodeName;
}); });
const state = controller.properties.integrationRuntimeState; const state = migrationService.properties.integrationRuntimeState;
if (state === 'Online') { if (state === 'Online') {
connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{ connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.CONTROLLER_READY(this.migrationStateModel._migrationController!.name, this.migrationStateModel._nodeNames.join(', ')), text: constants.SERVICE_READY(this.migrationStateModel._sqlMigrationService!.name, this.migrationStateModel._nodeNames.join(', ')),
style: 'success' style: 'success'
}); });
} else { } else {
connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{ connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name), text: constants.SERVICE_NOT_READY(this.migrationStateModel._sqlMigrationService!.name),
style: 'error' style: 'error'
}); });
} }
@@ -275,16 +279,16 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
connectionStatusLoader.loading = false; connectionStatusLoader.loading = false;
}); });
if (controller) { const state = migrationService.properties.integrationRuntimeState;
const state = controller.properties.integrationRuntimeState; if (migrationService) {
if (state === 'Online') { if (state === 'Online') {
connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{ connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.CONTROLLER_READY(this.migrationStateModel._migrationController!.name, this.migrationStateModel._nodeNames.join(', ')), text: constants.SERVICE_READY(this.migrationStateModel._sqlMigrationService!.name, this.migrationStateModel._nodeNames.join(', ')),
style: 'success' style: 'success'
}); });
} else { } else {
connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{ connectionStatus.updateProperties(<azdata.InfoBoxComponentProperties>{
text: constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name), text: constants.SERVICE_NOT_READY(this.migrationStateModel._sqlMigrationService!.name),
style: 'error' style: 'error'
}); });
} }
@@ -297,7 +301,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
} }
}).component(); }).component();
const migrationControllerAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({ const migrationServiceAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({
columns: [ columns: [
{ {
displayName: constants.NAME, displayName: constants.NAME,
@@ -341,8 +345,8 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}).component(); }).component();
copyKey1Button.onDidClick((e) => { copyKey1Button.onDidClick((e) => {
vscode.env.clipboard.writeText(<string>migrationControllerAuthKeyTable.dataValues![0][1].value); vscode.env.clipboard.writeText(<string>migrationServiceAuthKeyTable.dataValues![0][1].value);
vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); vscode.window.showInformationMessage(constants.SERVICE_KEY_COPIED_HELP);
}); });
const copyKey2Button = this._view.modelBuilder.button().withProps({ const copyKey2Button = this._view.modelBuilder.button().withProps({
@@ -350,8 +354,8 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
}).component(); }).component();
copyKey2Button.onDidClick((e) => { copyKey2Button.onDidClick((e) => {
vscode.env.clipboard.writeText(<string>migrationControllerAuthKeyTable.dataValues![1][1].value); vscode.env.clipboard.writeText(<string>migrationServiceAuthKeyTable.dataValues![1][1].value);
vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); vscode.window.showInformationMessage(constants.SERVICE_KEY_COPIED_HELP);
}); });
const refreshKey1Button = this._view.modelBuilder.button().withProps({ const refreshKey1Button = this._view.modelBuilder.button().withProps({
@@ -368,15 +372,14 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
refreshKey2Button.onDidClick((e) => {//TODO: add refresh logic refreshKey2Button.onDidClick((e) => {//TODO: add refresh logic
}); });
migrationServiceAuthKeyTable.updateProperties({
migrationControllerAuthKeyTable.updateProperties({
dataValues: [ dataValues: [
[ [
{ {
value: constants.CONTROLLER_KEY1_LABEL value: constants.SERVICE_KEY1_LABEL
}, },
{ {
value: migrationControllerAuthKeys.authKey1 value: migrationServiceAuthKeys.authKey1
}, },
{ {
value: copyKey1Button value: copyKey1Button
@@ -387,10 +390,10 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
], ],
[ [
{ {
value: constants.CONTROLLER_KEY2_LABEL value: constants.SERVICE_KEY2_LABEL
}, },
{ {
value: migrationControllerAuthKeys.authKey2 value: migrationServiceAuthKeys.authKey2
}, },
{ {
value: copyKey2Button value: copyKey2Button
@@ -404,14 +407,14 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
this._migrationDetailsContainer.addItems( this._migrationDetailsContainer.addItems(
[ [
migrationControllerTitle, migrationServiceTitle,
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name), createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name),
createInformationRow(this._view, constants.RESOURCE_GROUP, controller.properties.resourceGroup), createInformationRow(this._view, constants.RESOURCE_GROUP, migrationService.properties.resourceGroup),
createInformationRow(this._view, constants.LOCATION, controller.properties.location), createInformationRow(this._view, constants.LOCATION, migrationService.properties.location),
connectionLabelContainer, connectionLabelContainer,
connectionStatusLoader, connectionStatusLoader,
authenticationKeysLabel, authenticationKeysLabel,
migrationControllerAuthKeyTable migrationServiceAuthKeyTable
] ]
); );
} }

View File

@@ -4,18 +4,32 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as path from 'path';
import { MigrationWizardPage } from '../models/migrationWizardPage'; import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine'; import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import { Product, ProductLookupTable } from '../models/product'; import { Product } from '../models/product';
import { AssessmentResultsDialog } from '../dialog/assessmentResults/assessmentResultsDialog'; import { AssessmentResultsDialog } from '../dialog/assessmentResults/assessmentResultsDialog';
import * as constants from '../constants/strings'; import * as constants from '../constants/strings';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { EOL } from 'os'; import { EOL } from 'os';
import { IconPathHelper } from '../constants/iconPathHelper';
// import { SqlMigrationService } from '../../../../extensions/mssql/src/sqlMigration/sqlMigrationService'; // import { SqlMigrationService } from '../../../../extensions/mssql/src/sqlMigration/sqlMigrationService';
export class SKURecommendationPage extends MigrationWizardPage { export class SKURecommendationPage extends MigrationWizardPage {
private supportedProducts: Product[] = [
{
type: 'AzureSQLMI',
name: constants.SKU_RECOMMENDATION_MI_CARD_TEXT,
icon: IconPathHelper.sqlMiLogo
},
{
type: 'AzureSQLVM',
name: constants.SKU_RECOMMENDATION_VM_CARD_TEXT,
icon: IconPathHelper.sqlVmLogo
}
];
// For future reference: DO NOT EXPOSE WIZARD DIRECTLY THROUGH HERE. // For future reference: DO NOT EXPOSE WIZARD DIRECTLY THROUGH HERE.
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) { constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.SKU_RECOMMENDATION_PAGE_TITLE), migrationStateModel); super(wizard, azdata.window.createWizardPage(constants.SKU_RECOMMENDATION_PAGE_TITLE), migrationStateModel);
@@ -53,7 +67,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
if (e.selected) { if (e.selected) {
this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index); this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index);
this.migrationStateModel._targetServerInstance = undefined!; this.migrationStateModel._targetServerInstance = undefined!;
this.migrationStateModel._migrationController = undefined!; this.migrationStateModel._sqlMigrationService = undefined!;
this.populateResourceInstanceDropdown(); this.populateResourceInstanceDropdown();
} }
}); });
@@ -66,7 +80,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
if (e.selected && if (e.selected &&
e.selected !== constants.NO_MANAGED_INSTANCE_FOUND && e.selected !== constants.NO_MANAGED_INSTANCE_FOUND &&
e.selected !== constants.NO_VIRTUAL_MACHINE_FOUND) { e.selected !== constants.NO_VIRTUAL_MACHINE_FOUND) {
this.migrationStateModel._migrationControllers = undefined!; this.migrationStateModel._sqlMigrationServices = undefined!;
if (this._rbg.selectedCardId === 'AzureSQLVM') { if (this._rbg.selectedCardId === 'AzureSQLVM') {
this.migrationStateModel._targetServerInstance = this.migrationStateModel.getVirtualMachine(e.index); this.migrationStateModel._targetServerInstance = this.migrationStateModel.getVirtualMachine(e.index);
} else { } else {
@@ -179,7 +193,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
} }
private constructTargets(): void { private constructTargets(): void {
const products: Product[] = Object.values(ProductLookupTable); const products: Product[] = this.supportedProducts;
this._rbg = this._view!.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({ this._rbg = this._view!.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({
cards: [], cards: [],
@@ -191,7 +205,6 @@ export class SKURecommendationPage extends MigrationWizardPage {
}).component(); }).component();
products.forEach((product) => { products.forEach((product) => {
const imagePath = path.resolve(this.migrationStateModel.getExtensionPath(), 'media', product.icon ?? 'ads.svg');
let dbCount = 0; let dbCount = 0;
if (product.type === 'AzureSQLVM') { if (product.type === 'AzureSQLVM') {
dbCount = this._dbCount; dbCount = this._dbCount;
@@ -238,7 +251,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._rbg.cards.push({ this._rbg.cards.push({
id: product.type, id: product.type,
icon: imagePath, icon: product.icon,
descriptions descriptions
}); });
}); });
@@ -248,7 +261,6 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._rbg.onLinkClick(async (value) => { this._rbg.onLinkClick(async (value) => {
//check which card is being selected, and open correct dialog based on link //check which card is being selected, and open correct dialog based on link
console.log(value);
if (value.description.linkDisplayValue === 'View/Change') { if (value.description.linkDisplayValue === 'View/Change') {
if (value.cardId === 'AzureSQLVM') { if (value.cardId === 'AzureSQLVM') {
await vmDialog.openDialog(); await vmDialog.openDialog();

View File

@@ -46,7 +46,7 @@ export class SummaryPage extends MigrationWizardPage {
createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_PAGE_TITLE), createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_PAGE_TITLE),
this.createNetworkContainerRows(), this.createNetworkContainerRows(),
createHeadingTextComponent(this._view, constants.IR_PAGE_TITLE), createHeadingTextComponent(this._view, constants.IR_PAGE_TITLE),
createInformationRow(this._view, constants.IR_PAGE_TITLE, this.migrationStateModel._migrationController?.name!), createInformationRow(this._view, constants.IR_PAGE_TITLE, this.migrationStateModel._sqlMigrationService?.name!),
createInformationRow(this._view, constants.SUMMARY_IR_NODE, this.migrationStateModel._nodeNames.join(', ')), createInformationRow(this._view, constants.SUMMARY_IR_NODE, this.migrationStateModel._nodeNames.join(', ')),
] ]

View File

@@ -33,7 +33,7 @@ export class TempTargetSelectionPage extends MigrationWizardPage {
if (e.selected) { if (e.selected) {
this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index); this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index);
this.migrationStateModel._targetServerInstance = undefined!; this.migrationStateModel._targetServerInstance = undefined!;
this.migrationStateModel._migrationController = undefined!; this.migrationStateModel._sqlMigrationService = undefined!;
this.populateManagedInstanceDropdown(); this.populateManagedInstanceDropdown();
} }
}); });
@@ -48,7 +48,7 @@ export class TempTargetSelectionPage extends MigrationWizardPage {
}).component(); }).component();
this._managedInstanceDropdown.onValueChanged((e) => { this._managedInstanceDropdown.onValueChanged((e) => {
if (e.selected) { if (e.selected) {
this.migrationStateModel._migrationControllers = undefined!; this.migrationStateModel._sqlMigrationServices = undefined!;
this.migrationStateModel._targetServerInstance = this.migrationStateModel.getManagedInstance(e.index); this.migrationStateModel._targetServerInstance = this.migrationStateModel.getManagedInstance(e.index);
} }
}); });