diff --git a/extensions/sql-migration/src/api/azure.ts b/extensions/sql-migration/src/api/azure.ts index 94aeabeecf..b190fb6e5f 100644 --- a/extensions/sql-migration/src/api/azure.ts +++ b/extensions/sql-migration/src/api/azure.ts @@ -86,10 +86,10 @@ export async function getBlobContainers(account: azdata.Account, subscription: S return blobContainers!; } -export async function getMigrationController(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise { +export async function getSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise { const api = await getAzureCoreAPI(); 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); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -97,10 +97,10 @@ export async function getMigrationController(account: azdata.Account, subscripti return response.response.data; } -export async function getMigrationControllers(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string): Promise { +export async function getSqlMigrationServices(account: azdata.Account, subscription: Subscription, regionName: string): Promise { const api = await getAzureCoreAPI(); 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); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -109,10 +109,10 @@ export async function getMigrationControllers(account: azdata.Account, subscript return response.response.data.value; } -export async function createMigrationController(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise { +export async function createSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise { const api = await getAzureCoreAPI(); 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 = { 'location': regionName }; @@ -123,10 +123,10 @@ export async function createMigrationController(account: azdata.Account, subscri return response.response.data; } -export async function getMigrationControllerAuthKeys(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise { +export async function getSqlMigrationServiceAuthKeys(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise { const api = await getAzureCoreAPI(); 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); if (response.errors.length > 0) { 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 { +export async function getSqlMigrationServiceMonitoringData(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationService: string): Promise { const api = await getAzureCoreAPI(); 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); if (response.errors.length > 0) { 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 response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); 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()); } return response.response.data; @@ -198,10 +201,10 @@ export async function getMigrationStatus(account: azdata.Account, subscription: return response.response.data; } -export async function listMigrationsByController(account: azdata.Account, subscription: Subscription, controller: SqlMigrationController): Promise { +export async function listMigrationsBySqlMigrationService(account: azdata.Account, subscription: Subscription, sqlMigrationService: SqlMigrationService): Promise { const api = await getAzureCoreAPI(); 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); if (response.errors.length > 0) { 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. */ -export function getMigrationControllerRegions(): azdata.CategoryValue[] { +export function getSqlMigrationServiceRegions(): azdata.CategoryValue[] { return [ { 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 { if (!resourceArray) { return; @@ -258,7 +261,7 @@ function sortResourceArrayByName(resourceArray: SortableAzureResources[]): void }); } -export interface MigrationControllerProperties { +export interface SqlMigrationServiceProperties { name: string; subscriptionId: string; resourceGroup: string; @@ -268,8 +271,8 @@ export interface MigrationControllerProperties { isProvisioned?: boolean; } -export interface SqlMigrationController { - properties: MigrationControllerProperties; +export interface SqlMigrationService { + properties: SqlMigrationServiceProperties; location: string; id: string; name: string; @@ -279,7 +282,7 @@ export interface SqlMigrationController { } } -export interface GetMigrationControllerAuthKeysResult { +export interface SqlMigrationServiceAuthenticationKeys { authKey1: string, authKey2: string } @@ -289,12 +292,12 @@ export interface GetStorageAccountAccessKeysResult { keyName2: string } -export interface GetMigrationControllerMonitoringData { +export interface IntegrationRuntimeMonitoringData { name: string, - nodes: MigrationControllerNode[]; + nodes: IntegrationRuntimeNode[]; } -export interface MigrationControllerNode { +export interface IntegrationRuntimeNode { availableMemoryInMB: number, concurrentJobsLimit: number concurrentJobsRunning: number, @@ -307,27 +310,32 @@ export interface MigrationControllerNode { export interface StartDatabaseMigrationRequest { location: string, properties: { - SourceDatabaseName: string, - MigrationController: string, - BackupConfiguration: { - TargetLocation: { - StorageAccountResourceId: string, - AccountKey: string, + sourceDatabaseName: string, + migrationService: string, + backupConfiguration: { + targetLocation: { + storageAccountResourceId: string, + accountKey: string, } - SourceLocation: { - FileShare: { - Path: string, - Username: string, - Password: string, + sourceLocation: { + fileShare?: { + path: string, + username: string, + password: string, + }, + azureBlob?: { + storageAccountResourceId: string, + accountKey: string, + blobContainerName: string } }, }, - SourceSqlConnection: { - DataSource: string, - Username: string, - Password: string + sourceSqlConnection: { + dataSource: string, + username: string, + password: string }, - Scope: string + scope: string } } @@ -347,10 +355,12 @@ export interface DatabaseMigrationProperties { provisioningState: string; migrationStatus: string; migrationStatusDetails?: MigrationStatusDetails; + startedOn: string; + endedOn: string; sourceSqlConnection: SqlConnectionInfo; sourceDatabaseName: string; targetDatabaseCollation: string; - migrationController: string; + migrationService: string; migrationOperationId: string; backupConfiguration: BackupConfiguration; autoCutoverConfiguration: AutoCutoverConfiguration; diff --git a/extensions/sql-migration/src/api/utils.ts b/extensions/sql-migration/src/api/utils.ts index 6d1cfadce9..ee9ae1f294 100644 --- a/extensions/sql-migration/src/api/utils.ts +++ b/extensions/sql-migration/src/api/utils.ts @@ -21,3 +21,22 @@ export function deepClone(obj: T): T { }); 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; + } +} diff --git a/extensions/sql-migration/src/constants/iconPathHelper.ts b/extensions/sql-migration/src/constants/iconPathHelper.ts index cebaba14f4..418db19689 100644 --- a/extensions/sql-migration/src/constants/iconPathHelper.ts +++ b/extensions/sql-migration/src/constants/iconPathHelper.ts @@ -22,6 +22,8 @@ export class IconPathHelper { public static inProgressMigration: IconPath; public static completedMigration: IconPath; public static notStartedMigration: IconPath; + public static sqlVmLogo: IconPath; + public static sqlMiLogo: IconPath; public static setExtensionContext(context: vscode.ExtensionContext) { IconPathHelper.copy = { @@ -68,5 +70,13 @@ export class IconPathHelper { light: 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') + }; } } diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts index 7991e8b7c0..434adec539 100644 --- a/extensions/sql-migration/src/constants/strings.ts +++ b/extensions/sql-migration/src/constants/strings.ts @@ -3,6 +3,7 @@ * 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'; 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_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_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); } 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 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"); // integration runtime page -export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Migration Controller"); -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_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 SELECT_A_MIGRATION_CONTROLLER = localize('sql.migration.controller', "Select a migration controller"); -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 CUSTOM_SETUP_BUTTON = localize('sql.migration.custom.setup.button', "Custom setup: Add migration controller 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 IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Azure Database Migration Service"); +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: 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_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 DMS with one click express setup using default options."); +export const CUSTOM_SETUP_BUTTON = localize('sql.migration.custom.setup.button', "Custom setup: Add DMS after customizing most options."); +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 INVALID_CONTROLLER_ERROR = localize('sql.migration.invalid.controller.error', "Please select a valid controller"); -export const CONTROLLER_OFFLINE_ERROR = localize('sql.migration.invalid.controller.offline.error', "Please select a controller that is connected to a node"); +export const INVALID_SERVICE_ERROR = localize('sql.migration.invalid.migration.service.error', "Please select a valid DMS"); +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 function CONTROLLER_DETAILS_HEADER(controllerName: string) { - return localize('sql.migration.controller.header', "Migration Controller \"{0}\" details:`", controllerName); +export function SQL_MIGRATION_SERVICE_DETAILS_HEADER(sqlMigrationServiceName: string) { + return localize('sql.migration.service.header', "Azure Data Migration Service \"{0}\" details:`", sqlMigrationServiceName); } -// create migration controller 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 CONTROLLER_DIALOG_CONTROLLER_CONTAINER_LOADING_HELP = localize('sql.migration.controller.container.loading.help', "Loading Controller"); -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 CONTROLLER_DIALOG_CONTROLLER_CONTAINER_HEADING = localize('sql.migration.controller.container.heading', "Setup Integration Runtime"); -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 CONTROLLER_STEP1 = localize('sql.migration.ir.setup.step1', "Step 1: {0}"); -export const CONTROLLER_STEP1_LINK = localize('sql.migration.option', "Download and install integration runtime"); -export const CONTROLLER_STEP2 = localize('sql.migration.ir.setup.step2', "Step 2: Use this key to register your integration runtime"); -export const CONTROLLER_STEP3 = localize('sql.migration.ir.setup.step3', "Step 3: Check connection"); -export const CONTROLLER_CONNECTION_STATUS = localize('sql.migration.connection.status', "Connection Status"); -export const CONTROLLER_KEY1_LABEL = localize('sql.migration.key1.label', "Key 1"); -export const CONTROLLER_KEY2_LABEL = localize('sql.migration.key2.label', "Key 2"); -export const CONTROLLER_KEY_COPIED_HELP = localize('sql.migration.key.copied', "Key copied"); +// create migration service dialog +export const CREATE_MIGRATION_SERVICE_TITLE = localize('sql.migration.services.dialog.title', "Create Azure Data Migration Service"); +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 LOADING_MIGRATION_SERVICES = localize('sql.migration.service.container.loading.help', "Loading Migration Services"); +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 SERVICE_CONTAINER_HEADING = localize('sql.migration.service.container.heading', "Setup Integration Runtime"); +export const SERVICE_CONTAINER_DESCRIPTION = localize('sql.migration.service.container.container.description', "Follow the instructions below to setup self-hosted Integration Runtime."); +export const SERVICE_STEP1 = localize('sql.migration.ir.setup.step1', "Step 1: {0}"); +export const SERVICE_STEP1_LINK = localize('sql.migration.option', "Download and install integration runtime"); +export const SERVICE_STEP2 = localize('sql.migration.ir.setup.step2', "Step 2: Use this key to register your integration runtime"); +export const SERVICE_STEP3 = localize('sql.migration.ir.setup.step3', "Step 3: Check connection"); +export const SERVICE_CONNECTION_STATUS = localize('sql.migration.connection.status', "Connection Status"); +export const SERVICE_KEY1_LABEL = localize('sql.migration.key1.label', "Key 1"); +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 COPY_KEY = localize('sql.migration.copy.key', "Copy key"); export const AUTH_KEY_COLUMN_HEADER = localize('sql.migration.authkeys.header', "Authentication key"); -export function CONTROLLER_NOT_READY(controllerName: string): string { - return localize('sql.migration.controller.not.ready', "Migration Controller {0} is not connected to self-hosted Integration Runtime on any node.", controllerName); +export function SERVICE_NOT_READY(serviceName: string): string { + 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 { - return localize('sql.migration.controller.ready', "Migration Controller '{0}' is connected to self-hosted Integration Runtime on the node - {1}", controllerName, host); +export function SERVICE_READY(serviceName: string, host: string): string { + 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 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_CONTROLLER_NAME_ERROR = localize('sql.migration.invalid.controller.name.error', "Please enter a valid name for the migration controller."); -export const CONTROLLER_NOT_FOUND = localize('sql.migration.controller.not.found', "No Migration Controllers 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 INVALID_SERVICE_NAME_ERROR = localize('sql.migration.invalid.service.name.error', "Please enter a valid name for the Migration Service."); +export const SERVICE_NOT_FOUND = localize('sql.migration.service.not.found', "No Migration Services found. Please create a new one."); +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 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"); @@ -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 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 COMING_SOON = localize('sql.migration.coming.soon', "Coming soon"); // Azure APIs export const EASTUS2EUAP = localize('sql.migration.eastus2euap', 'East US 2 EUAP'); - //Migration cutover dialog export const MIGRATION_CUTOVER = localize('sql.migration.cutover', "Migration cutover"); 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 export const SEARCH_FOR_MIGRATIONS = localize('sql.migration.search.for.migration', "Search for migrations"); 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 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"); diff --git a/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts index 026108ec1f..e0319c4597 100644 --- a/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts +++ b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts @@ -8,7 +8,6 @@ import * as vscode from 'vscode'; import { MigrationContext, MigrationLocalStorage } from '../models/migrationLocalStorage'; import * as loc from '../constants/strings'; import { IconPath, IconPathHelper } from '../constants/iconPathHelper'; -import { getDatabaseMigration } from '../api/azure'; import { MigrationStatusDialog } from '../dialog/migrationStatus/migrationStatusDialog'; import { MigrationCategory } from '../dialog/migrationStatus/migrationStatusDialogModel'; @@ -22,12 +21,25 @@ interface IActionMetadata { const maxWidth = 800; +interface StatusCard { + container: azdata.DivContainer; + count: azdata.TextComponent +} + export class DashboardWidget { private _migrationStatusCardsContainer!: azdata.FlexContainer; private _migrationStatusCardLoadingContainer!: azdata.LoadingComponent; private _view!: azdata.ModelView; + + private _inProgressMigrationButton!: StatusCard; + private _successfulMigrationButton!: StatusCard; + private _notStartedMigrationCard!: StatusCard; + private _migrationStatus!: MigrationContext[]; + + private _viewAllMigrationsButton!: azdata.ButtonComponent; + constructor() { } @@ -143,6 +155,10 @@ export class DashboardWidget { } }).component(); + preRequisiteLearnMoreLink.onDidClick((value) => { + vscode.window.showInformationMessage(loc.COMING_SOON); + }); + const preReqContainer = view.modelBuilder.flexContainer().withItems([ preRequisiteListTitle, preRequisiteListElement @@ -197,101 +213,51 @@ export class DashboardWidget { } private async refreshMigrations(): Promise { + this._viewAllMigrationsButton.enabled = false; this._migrationStatusCardLoadingContainer.loading = true; - this._migrationStatusCardsContainer.clearItems(); 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; 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; return status === 'Succeeded'; }); - const successfulMigrationButton = this.createStatusCard( - IconPathHelper.completedMigration, - 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 - ); + + this._successfulMigrationButton.count.value = successfulMigration.length.toString(); const currentConnection = (await azdata.connection.getCurrentConnection()); - const localMigrations = MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection); const migrationDatabases = new Set( - localMigrations.filter((value) => { - - }).map((value) => { + this._migrationStatus.map((value) => { return value.migrationContext.properties.sourceDatabaseName; })); const serverDatabases = await azdata.connection.listDatabases(currentConnection.connectionId); - const notStartedMigrationCard = this.createStatusCard( - 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 - ); + this._notStartedMigrationCard.count.value = (serverDatabases.length - migrationDatabases.size).toString(); } catch (error) { console.log(error); } finally { this._migrationStatusCardLoadingContainer.loading = false; + this._viewAllMigrationsButton.enabled = true; } } private async getMigrations(): Promise { const currentConnection = (await azdata.connection.getCurrentConnection()); - const localMigrations = MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection); - 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; + return await MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection, true); } private createStatusCard( cardIconPath: IconPath, cardTitle: string, - cardDescription: string, - count: number - ): azdata.DivContainer { + cardDescription: string + ): StatusCard { const cardTitleText = this._view.modelBuilder.text().withProps({ value: cardTitle }).withProps({ CSSStyles: { @@ -312,7 +278,7 @@ export class DashboardWidget { } }).component(); const cardCount = this._view.modelBuilder.text().withProps({ - value: count.toString(), + value: '0', CSSStyles: { 'font-size': '28px', 'line-height': '36px', @@ -367,7 +333,10 @@ export class DashboardWidget { ariaLabel: 'show status', clickable: true }).component(); - return compositeButton; + return { + container: compositeButton, + count: cardCount + }; } private async createFooter(view: azdata.ModelView): Promise { @@ -412,7 +381,7 @@ export class DashboardWidget { } }).component(); - const viewAllButton = view.modelBuilder.hyperlink().withProps({ + this._viewAllMigrationsButton = view.modelBuilder.hyperlink().withProps({ label: loc.VIEW_ALL, url: '', CSSStyles: { @@ -420,8 +389,8 @@ export class DashboardWidget { } }).component(); - viewAllButton.onDidClick(async (e) => { - new MigrationStatusDialog(await this.getMigrations(), MigrationCategory.ALL).initialize(); + this._viewAllMigrationsButton.onDidClick(async (e) => { + new MigrationStatusDialog(this._migrationStatus ? this._migrationStatus : await this.getMigrations(), MigrationCategory.ALL).initialize(); }); const refreshButton = view.modelBuilder.hyperlink().withProps({ @@ -433,15 +402,17 @@ export class DashboardWidget { } }).component(); - refreshButton.onDidClick((e) => { - this.refreshMigrations(); + refreshButton.onDidClick(async (e) => { + refreshButton.enabled = false; + await this.refreshMigrations(); + refreshButton.enabled = true; }); const buttonContainer = view.modelBuilder.flexContainer().withLayout({ justifyContent: 'flex-end', }).component(); - buttonContainer.addItem(viewAllButton, { + buttonContainer.addItem(this._viewAllMigrationsButton, { flex: 'auto', CSSStyles: { 'border-right': '1px solid ', @@ -466,9 +437,46 @@ export class DashboardWidget { flexFlow: 'row' }).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(); statusContainer.addItem( diff --git a/extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts b/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts similarity index 63% rename from extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts rename to extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts index a1d432e321..bed802753e 100644 --- a/extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts +++ b/extensions/sql-migration/src/dialog/createSqlMigrationService/createSqlMigrationServiceDialog.ts @@ -5,7 +5,7 @@ import * as azdata from 'azdata'; 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 * as constants from '../../constants/strings'; import * as os from 'os'; @@ -13,16 +13,16 @@ import { azureResource } from 'azureResource'; import { IntergrationRuntimePage } from '../../wizard/integrationRuntimePage'; import { IconPathHelper } from '../../constants/iconPathHelper'; -export class CreateMigrationControllerDialog { +export class CreateSqlMigrationServiceDialog { - private migrationControllerSubscriptionDropdown!: azdata.DropDownComponent; - private migrationControllerResourceGroupDropdown!: azdata.DropDownComponent; - private migrationControllerRegionDropdown!: azdata.DropDownComponent; - private migrationControllerNameText!: azdata.InputBoxComponent; + private migrationServiceSubscriptionDropdown!: azdata.DropDownComponent; + private migrationServiceResourceGroupDropdown!: azdata.DropDownComponent; + private migrationServiceRegionDropdown!: azdata.DropDownComponent; + private migrationServiceNameText!: azdata.InputBoxComponent; private _formSubmitButton!: azdata.ButtonComponent; private _statusLoadingComponent!: azdata.LoadingComponent; - private migrationControllerAuthKeyTable!: azdata.DeclarativeTableComponent; + private migrationServiceAuthKeyTable!: azdata.DeclarativeTableComponent; private _connectionStatus!: azdata.InfoBoxComponent; private _copyKey1Button!: azdata.ButtonComponent; private _copyKey2Button!: azdata.ButtonComponent; @@ -33,8 +33,11 @@ export class CreateMigrationControllerDialog { private _dialogObject!: azdata.window.Dialog; private _view!: azdata.ModelView; + private createdMigrationService!: SqlMigrationService; + private createdMigrationServiceNodeNames!: string[]; + 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() { @@ -51,15 +54,18 @@ export class CreateMigrationControllerDialog { }).component(); this._formSubmitButton.onDidClick(async (e) => { + this._dialogObject.message = { + text: '' + }; this._statusLoadingComponent.loading = true; this._formSubmitButton.enabled = false; const subscription = this.migrationStateModel._targetSubscription; - const resourceGroup = (this.migrationControllerResourceGroupDropdown.value as azdata.CategoryValue).name; - const region = (this.migrationControllerRegionDropdown.value as azdata.CategoryValue).name; - const controllerName = this.migrationControllerNameText.value; + const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name; + const region = (this.migrationServiceRegionDropdown.value as azdata.CategoryValue).name; + 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) { this.setDialogMessage(formValidationErrors); @@ -69,9 +75,9 @@ export class CreateMigrationControllerDialog { } try { - const createdController = await createMigrationController(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, controllerName!); - if (createdController.error) { - this.setDialogMessage(`${createdController.error.code} : ${createdController.error.message}`); + this.createdMigrationService = await createSqlMigrationService(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, serviceName!); + if (this.createdMigrationService.error) { + this.setDialogMessage(`${this.createdMigrationService.error.code} : ${this.createdMigrationService.error.message}`); this._statusLoadingComponent.loading = false; this._formSubmitButton.enabled = true; return; @@ -79,13 +85,13 @@ export class CreateMigrationControllerDialog { this._dialogObject.message = { text: '' }; - this.migrationStateModel._migrationController = createdController; await this.refreshAuthTable(); await this.refreshStatus(); this._setupContainer.display = 'inline'; this._statusLoadingComponent.loading = false; } catch (e) { console.log(e); + this.setDialogMessage(e.message); this._statusLoadingComponent.loading = false; this._formSubmitButton.enabled = true; return; @@ -93,16 +99,16 @@ export class CreateMigrationControllerDialog { }); this._statusLoadingComponent = view.modelBuilder.loadingComponent().withProps({ - loadingText: constants.CONTROLLER_DIALOG_CONTROLLER_CONTAINER_LOADING_HELP, + loadingText: constants.LOADING_MIGRATION_SERVICES, loading: false }).component(); - const creationStatusContainer = this.createControllerStatus(); + const creationStatusContainer = this.createServiceStatus(); const formBuilder = view.modelBuilder.formContainer().withFormItems( [ { - component: this.migrationControllerDropdownsContainer() + component: this.migrationServiceDropdownContainer() }, { component: this._formSubmitButton @@ -132,36 +138,30 @@ export class CreateMigrationControllerDialog { this._dialogObject.cancelButton.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({ - value: constants.IR_PAGE_DESCRIPTION, - links: [ - { - text: constants.LEARN_MORE, - url: 'https://www.microsoft.com' // TODO: add a proper link to the docs. - } - ] + value: constants.MIGRATION_SERVICE_DIALOG_DESCRIPTION }).component(); const formHeading = this._view.modelBuilder.text().withProps({ - value: constants.CONTROLLER_DIALOG_CREATE_CONTROLLER_FORM_HEADING + value: constants.CREATE_SERVICE_FORM_HEADING }).component(); const subscriptionDropdownLabel = this._view.modelBuilder.text().withProps({ value: constants.SUBSCRIPTION }).component(); - this.migrationControllerSubscriptionDropdown = this._view.modelBuilder.dropDown().withProps({ + this.migrationServiceSubscriptionDropdown = this._view.modelBuilder.dropDown().withProps({ required: true, enabled: false }).component(); - this.migrationControllerSubscriptionDropdown.onValueChanged((e) => { - if (this.migrationControllerSubscriptionDropdown.value) { + this.migrationServiceSubscriptionDropdown.onValueChanged((e) => { + if (this.migrationServiceSubscriptionDropdown.value) { this.populateResourceGroups(); } }); @@ -170,43 +170,43 @@ export class CreateMigrationControllerDialog { value: constants.RESOURCE_GROUP }).component(); - this.migrationControllerResourceGroupDropdown = this._view.modelBuilder.dropDown().withProps({ + this.migrationServiceResourceGroupDropdown = this._view.modelBuilder.dropDown().withProps({ required: true }).component(); - const controllerNameLabel = this._view.modelBuilder.text().withProps({ + const migrationServiceNameLabel = this._view.modelBuilder.text().withProps({ value: constants.NAME }).component(); - this.migrationControllerNameText = this._view.modelBuilder.inputBox().component(); + this.migrationServiceNameText = this._view.modelBuilder.inputBox().component(); const regionsDropdownLabel = this._view.modelBuilder.text().withProps({ value: constants.REGION }).component(); - this.migrationControllerRegionDropdown = this._view.modelBuilder.dropDown().withProps({ + this.migrationServiceRegionDropdown = this._view.modelBuilder.dropDown().withProps({ required: true, - values: getMigrationControllerRegions() + values: getSqlMigrationServiceRegions() }).component(); const flexContainer = this._view.modelBuilder.flexContainer().withItems([ dialogDescription, formHeading, subscriptionDropdownLabel, - this.migrationControllerSubscriptionDropdown, + this.migrationServiceSubscriptionDropdown, resourceGroupDropdownLabel, - this.migrationControllerResourceGroupDropdown, - controllerNameLabel, - this.migrationControllerNameText, + this.migrationServiceResourceGroupDropdown, + migrationServiceNameLabel, + this.migrationServiceNameText, regionsDropdownLabel, - this.migrationControllerRegionDropdown + this.migrationServiceRegionDropdown ]).withLayout({ flexFlow: 'column' }).component(); 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[] = []; if (!subscription) { errors.push(constants.INVALID_SUBSCRIPTION_ERROR); @@ -217,29 +217,29 @@ export class CreateMigrationControllerDialog { if (!region) { errors.push(constants.INVALID_REGION_ERROR); } - if (!controllerName || controllerName.length === 0) { - errors.push(constants.INVALID_CONTROLLER_NAME_ERROR); + if (!migrationServiceName || migrationServiceName.length === 0) { + errors.push(constants.INVALID_SERVICE_NAME_ERROR); } return errors.join(os.EOL); } private async populateSubscriptions(): Promise { - this.migrationControllerSubscriptionDropdown.loading = true; - this.migrationControllerResourceGroupDropdown.loading = true; + this.migrationServiceSubscriptionDropdown.loading = true; + this.migrationServiceResourceGroupDropdown.loading = true; - this.migrationControllerSubscriptionDropdown.values = [ + this.migrationServiceSubscriptionDropdown.values = [ { displayName: this.migrationStateModel._targetSubscription.name, name: '' } ]; - this.migrationControllerSubscriptionDropdown.loading = false; + this.migrationServiceSubscriptionDropdown.loading = false; this.populateResourceGroups(); } private async populateResourceGroups(): Promise { - this.migrationControllerResourceGroupDropdown.loading = true; + this.migrationServiceResourceGroupDropdown.loading = true; let subscription = this.migrationStateModel._targetSubscription; const resourceGroups = await getResourceGroups(this.migrationStateModel._azureAccount, subscription); let resourceGroupDropdownValues: azdata.CategoryValue[] = []; @@ -258,39 +258,39 @@ export class CreateMigrationControllerDialog { } ]; } - this.migrationControllerResourceGroupDropdown.values = resourceGroupDropdownValues; - this.migrationControllerResourceGroupDropdown.loading = false; + this.migrationServiceResourceGroupDropdown.values = resourceGroupDropdownValues; + this.migrationServiceResourceGroupDropdown.loading = false; } - private createControllerStatus(): azdata.FlexContainer { + private createServiceStatus(): azdata.FlexContainer { const setupIRHeadingText = this._view.modelBuilder.text().withProps({ - value: constants.CONTROLLER_DIALOG_CONTROLLER_CONTAINER_HEADING, + value: constants.SERVICE_CONTAINER_HEADING, CSSStyles: { 'font-weight': 'bold' } }).component(); const setupIRdescription = this._view.modelBuilder.text().withProps({ - value: constants.CONTROLLER_DIALOG_CONTROLLER_CONTAINER_DESCRIPTION, + value: constants.SERVICE_CONTAINER_DESCRIPTION, }).component(); const irSetupStep1Text = this._view.modelBuilder.text().withProps({ - value: constants.CONTROLLER_STEP1, + value: constants.SERVICE_STEP1, links: [ { - text: constants.CONTROLLER_STEP1_LINK, + text: constants.SERVICE_STEP1_LINK, url: 'https://www.microsoft.com/download/details.aspx?id=39717' } ] }).component(); const irSetupStep2Text = this._view.modelBuilder.text().withProps({ - value: constants.CONTROLLER_STEP2 + value: constants.SERVICE_STEP2 }).component(); const irSetupStep3Text = this._view.modelBuilder.hyperlink().withProps({ - label: constants.CONTROLLER_STEP3, + label: constants.SERVICE_STEP3, url: '', CSSStyles: { 'margin-top': '10px', @@ -326,7 +326,7 @@ export class CreateMigrationControllerDialog { }).component(); - this.migrationControllerAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({ + this.migrationServiceAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({ columns: [ { displayName: constants.NAME, @@ -370,7 +370,7 @@ export class CreateMigrationControllerDialog { setupIRdescription, irSetupStep1Text, irSetupStep2Text, - this.migrationControllerAuthKeyTable, + this.migrationServiceAuthKeyTable, irSetupStep3Text, this._connectionStatus, refreshLoadingIndicator @@ -389,26 +389,26 @@ export class CreateMigrationControllerDialog { private async refreshStatus(): Promise { const subscription = this.migrationStateModel._targetSubscription; - const resourceGroup = (this.migrationControllerResourceGroupDropdown.value as azdata.CategoryValue).name; - const region = (this.migrationControllerRegionDropdown.value as azdata.CategoryValue).name; - const controllerStatus = await getMigrationController(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.migrationStateModel._migrationController!.name); - const controllerMonitoringStatus = await getMigrationControllerMonitoringData(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.migrationStateModel._migrationController!.name); - this.migrationStateModel._nodeNames = controllerMonitoringStatus.nodes.map((node) => { + const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name; + const region = (this.migrationServiceRegionDropdown.value as azdata.CategoryValue).name; + const migrationServiceStatus = await getSqlMigrationService(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.createdMigrationService!.name); + const migrationServiceMonitoringStatus = await getSqlMigrationServiceMonitoringData(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.createdMigrationService!.name); + this.createdMigrationServiceNodeNames = migrationServiceMonitoringStatus.nodes.map((node) => { return node.nodeName; }); - if (controllerStatus) { - const state = controllerStatus.properties.integrationRuntimeState; + if (migrationServiceStatus) { + const state = migrationServiceStatus.properties.integrationRuntimeState; if (state === 'Online') { this._connectionStatus.updateProperties({ - text: constants.CONTROLLER_READY(this.migrationStateModel._migrationController!.name, this.migrationStateModel._nodeNames.join(', ')), + text: constants.SERVICE_READY(this.createdMigrationService!.name, this.createdMigrationServiceNodeNames.join(', ')), style: 'success' }); this._dialogObject.okButton.enabled = true; } 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({ - text: constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name), + text: constants.SERVICE_NOT_READY(this.createdMigrationService!.name), style: 'warning' }); this._dialogObject.okButton.enabled = false; @@ -418,17 +418,17 @@ export class CreateMigrationControllerDialog { } private async refreshAuthTable(): Promise { const subscription = this.migrationStateModel._targetSubscription; - const resourceGroup = (this.migrationControllerResourceGroupDropdown.value as azdata.CategoryValue).name; - const region = (this.migrationControllerRegionDropdown.value as azdata.CategoryValue).name; - const keys = await getMigrationControllerAuthKeys(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.migrationStateModel._migrationController!.name); + const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name; + const region = (this.migrationServiceRegionDropdown.value as azdata.CategoryValue).name; + const keys = await getSqlMigrationServiceAuthKeys(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.createdMigrationService!.name); this._copyKey1Button = this._view.modelBuilder.button().withProps({ iconPath: IconPathHelper.copy }).component(); this._copyKey1Button.onDidClick((e) => { - vscode.env.clipboard.writeText(this.migrationControllerAuthKeyTable.dataValues![0][1].value); - vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); + vscode.env.clipboard.writeText(this.migrationServiceAuthKeyTable.dataValues![0][1].value); + vscode.window.showInformationMessage(constants.SERVICE_KEY_COPIED_HELP); }); this._copyKey2Button = this._view.modelBuilder.button().withProps({ @@ -436,8 +436,8 @@ export class CreateMigrationControllerDialog { }).component(); this._copyKey2Button.onDidClick((e) => { - vscode.env.clipboard.writeText(this.migrationControllerAuthKeyTable.dataValues![1][1].value); - vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); + vscode.env.clipboard.writeText(this.migrationServiceAuthKeyTable.dataValues![1][1].value); + vscode.window.showInformationMessage(constants.SERVICE_KEY_COPIED_HELP); }); this._refreshKey1Button = this._view.modelBuilder.button().withProps({ @@ -454,11 +454,11 @@ export class CreateMigrationControllerDialog { this._refreshKey2Button.onDidClick((e) => { //TODO: add refresh logic }); - this.migrationControllerAuthKeyTable.updateProperties({ + this.migrationServiceAuthKeyTable.updateProperties({ dataValues: [ [ { - value: constants.CONTROLLER_KEY1_LABEL + value: constants.SERVICE_KEY1_LABEL }, { value: keys.authKey1 @@ -472,7 +472,7 @@ export class CreateMigrationControllerDialog { ], [ { - value: constants.CONTROLLER_KEY2_LABEL + value: constants.SERVICE_KEY2_LABEL }, { value: keys.authKey2 diff --git a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts index e11b9b568e..1b95fa0e4d 100644 --- a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts +++ b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialog.ts @@ -8,15 +8,17 @@ import { IconPathHelper } from '../../constants/iconPathHelper'; import { MigrationContext } from '../../models/migrationLocalStorage'; import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel'; import * as loc from '../../constants/strings'; +import { getSqlServerName } from '../../api/utils'; export class MigrationCutoverDialog { private _dialogObject!: azdata.window.Dialog; private _view!: azdata.ModelView; private _model: MigrationCutoverDialogModel; private _databaseTitleName!: azdata.TextComponent; - private _databaseCutoverButton!: azdata.ButtonComponent; - private _refresh!: azdata.ButtonComponent; - private _cancel!: azdata.ButtonComponent; + private _cutoverButton!: azdata.ButtonComponent; + private _refreshButton!: azdata.ButtonComponent; + private _cancelButton!: azdata.ButtonComponent; + private _refreshLoader!: azdata.LoadingComponent; private _serverName!: azdata.TextComponent; private _serverVersion!: azdata.TextComponent; @@ -43,7 +45,7 @@ export class MigrationCutoverDialog { let tab = azdata.window.createTab(''); tab.registerContent(async (view: azdata.ModelView) => { this._view = view; - const sourceDetails = this.createInfoField(loc.SOURCE_VERSION, ''); + const sourceDetails = this.createInfoField(loc.SOURCE_SERVER, ''); const sourceVersion = this.createInfoField(loc.SOURCE_VERSION, ''); this._serverName = sourceDetails.text; @@ -183,30 +185,30 @@ export class MigrationCutoverDialog { columns: [ { value: loc.ACTIVE_BACKUP_FILES, - width: 150, + width: 280, type: azdata.ColumnType.text }, { value: loc.TYPE, - width: 100, + width: 90, type: azdata.ColumnType.text }, { value: loc.STATUS, - width: 100, + width: 60, type: azdata.ColumnType.text }, { value: loc.BACKUP_START_TIME, - width: 150, + width: 130, type: azdata.ColumnType.text }, { value: loc.FIRST_LSN, - width: 150, + width: 120, type: azdata.ColumnType.text }, { value: loc.LAST_LSN, - width: 150, + width: 120, type: azdata.ColumnType.text } ], @@ -235,11 +237,12 @@ export class MigrationCutoverDialog { } ); const form = formBuilder.withLayout({ width: '100%' }).component(); - return view.initializeModel(form); + return view.initializeModel(form).then((value) => { + this.refreshStatus(); + }); }); this._dialogObject.content = [tab]; 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, iconHeight: '14px', iconWidth: '12px', @@ -272,7 +275,7 @@ export class MigrationCutoverDialog { enabled: false }).component(); - this._databaseCutoverButton.onDidClick(async (e) => { + this._cutoverButton.onDidClick(async (e) => { if (this._startCutover) { await this._model.startCutover(); this.refreshStatus(); @@ -284,14 +287,14 @@ export class MigrationCutoverDialog { } }); - header.addItem(this._databaseCutoverButton, { + header.addItem(this._cutoverButton, { flex: '0', CSSStyles: { 'width': '100px' } }); - this._cancel = this._view.modelBuilder.button().withProps({ + this._cancelButton = this._view.modelBuilder.button().withProps({ iconPath: IconPathHelper.discard, iconHeight: '16px', iconWidth: '16px', @@ -300,11 +303,11 @@ export class MigrationCutoverDialog { width: '130px' }).component(); - this._cancel.onDidClick((e) => { + this._cancelButton.onDidClick((e) => { this.cancelMigration(); }); - header.addItem(this._cancel, { + header.addItem(this._cancelButton, { flex: '0', CSSStyles: { '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, iconHeight: '16px', iconWidth: '16px', @@ -321,28 +324,39 @@ export class MigrationCutoverDialog { width: '100px' }).component(); - this._refresh.onDidClick((e) => { + this._refreshButton.onDidClick((e) => { this.refreshStatus(); }); - header.addItem(this._refresh, { + header.addItem(this._refreshButton, { flex: '0', CSSStyles: { 'width': '100px' } }); + this._refreshLoader = this._view.modelBuilder.loadingComponent().withProps({ + loading: false, + height: '55px' + }).component(); + + header.addItem(this._refreshLoader, { + flex: '0' + }); return header; } private async refreshStatus(): Promise { try { + this._refreshLoader.loading = true; + this._cutoverButton.enabled = false; + this._cancelButton.enabled = false; await this._model.fetchStatus(); const sqlServerInfo = await azdata.connection.getServerInfo(this._model._migration.sourceConnectionProfile.connectionId); const sqlServerName = this._model._migration.sourceConnectionProfile.serverName; - const sqlServerVersion = sqlServerInfo.serverVersion; - const sqlServerEdition = sqlServerInfo.serverEdition; + const versionName = getSqlServerName(sqlServerInfo.serverMajorVersion!); + const sqlServerVersion = versionName ? versionName : sqlServerInfo.serverVersion; const targetServerName = this._model._migration.targetManagedInstance.name; let targetServerVersion; if (this._model.migrationStatus.id.includes('managedInstances')) { @@ -382,7 +396,7 @@ export class MigrationCutoverDialog { this._serverName.value = sqlServerName; this._serverVersion.value = `${sqlServerVersion} - ${sqlServerEdition}`; + ${sqlServerInfo.serverVersion}`; this._targetServer.value = targetServerName; this._targetVersion.value = targetServerVersion; @@ -396,6 +410,9 @@ export class MigrationCutoverDialog { 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) => { return [ row.fileName, @@ -411,14 +428,17 @@ export class MigrationCutoverDialog { } 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 { - this._databaseCutoverButton.enabled = false; - this._cancel.enabled = false; + this._cutoverButton.enabled = false; + this._cancelButton.enabled = false; } } catch (e) { console.log(e); } + this._refreshLoader.loading = false; } private createInfoField(label: string, value: string): { diff --git a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts index d66df04a73..91d366d895 100644 --- a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts +++ b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts @@ -19,6 +19,7 @@ export class MigrationStatusDialog { private _refresh!: azdata.ButtonComponent; private _statusDropdown!: azdata.DropDownComponent; private _statusTable!: azdata.DeclarativeTableComponent; + private _refreshLoader!: azdata.LoadingComponent; constructor(migrations: MigrationContext[], private _filter: MigrationCategory) { 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; } private populateMigrationTable(): void { - try { const migrations = this._model.filterMigration( this._searchBox.value!, @@ -115,6 +124,10 @@ export class MigrationStatusDialog { 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) => { const migrationRow: azdata.DeclarativeTableCellValue[] = []; @@ -133,8 +146,8 @@ export class MigrationStatusDialog { value: migration.migrationContext.properties.migrationStatus }); - const sqlMigrationIcon = this._view.modelBuilder.image().withProps({ - iconPath: IconPathHelper.sqlMigrationLogo, + const targetMigrationIcon = this._view.modelBuilder.image().withProps({ + iconPath: (migration.targetManagedInstance.type === 'microsoft.sql/managedinstances') ? IconPathHelper.sqlMiLogo : IconPathHelper.sqlVmLogo, iconWidth: '16px', iconHeight: '16px', width: '32px', @@ -145,7 +158,7 @@ export class MigrationStatusDialog { url: '' }).component(); sqlMigrationName.onDidClick((e) => { - vscode.window.showInformationMessage('Feature coming soon'); + vscode.window.showInformationMessage(loc.COMING_SOON); }); const sqlMigrationContainer = this._view.modelBuilder.flexContainer().withProps({ @@ -153,7 +166,7 @@ export class MigrationStatusDialog { 'justify-content': 'center' } }).component(); - sqlMigrationContainer.addItem(sqlMigrationIcon, { + sqlMigrationContainer.addItem(targetMigrationIcon, { flex: '0', CSSStyles: { 'width': '32px' @@ -174,10 +187,10 @@ export class MigrationStatusDialog { }); migrationRow.push({ - value: '---' + value: (migration.migrationContext.properties.startedOn) ? new Date(migration.migrationContext.properties.startedOn).toLocaleString() : '---' }); migrationRow.push({ - value: '---' + value: (migration.migrationContext.properties.endedOn) ? new Date(migration.migrationContext.properties.endedOn).toLocaleString() : '---' }); data.push(migrationRow); @@ -190,6 +203,7 @@ export class MigrationStatusDialog { } private refreshTable(): void { + this._refreshLoader.loading = true; this._model._migrations.forEach(async (migration) => { migration.migrationContext = await getDatabaseMigration( migration.azureAccount, @@ -198,8 +212,8 @@ export class MigrationStatusDialog { migration.migrationContext.id ); }); - this.populateMigrationTable(); + this._refreshLoader.loading = false; } private createStatusTable(): azdata.DeclarativeTableComponent { diff --git a/extensions/sql-migration/src/models/migrationLocalStorage.ts b/extensions/sql-migration/src/models/migrationLocalStorage.ts index 5388bc4b2f..7e5a27d26e 100644 --- a/extensions/sql-migration/src/models/migrationLocalStorage.ts +++ b/extensions/sql-migration/src/models/migrationLocalStorage.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import { azureResource } from 'azureResource'; -import { DatabaseMigration, SqlMigrationController, SqlManagedInstance } from '../api/azure'; +import { DatabaseMigration, SqlMigrationService, SqlManagedInstance, getDatabaseMigration } from '../api/azure'; import * as azdata from 'azdata'; @@ -16,23 +16,39 @@ export class MigrationLocalStorage { MigrationLocalStorage.context = context; } - public static getMigrationsBySourceConnections(connectionProfile: azdata.connection.ConnectionProfile): MigrationContext[] { + public static async getMigrationsBySourceConnections(connectionProfile: azdata.connection.ConnectionProfile, refreshStatus?: boolean): Promise { - let dataBaseMigrations: MigrationContext[] = []; - try { - const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || []; + const result: MigrationContext[] = []; + const validMigrations: MigrationContext[] = []; - dataBaseMigrations = migrationMementos.filter((memento) => { - return memento.sourceConnectionProfile.serverName === connectionProfile.serverName; - }).map((memento) => { - return memento; - }); - } catch (e) { - console.log(e); + const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || []; + for (let i = 0; i < migrationMementos.length; i++) { + const migration = migrationMementos[i]; + if (migration.sourceConnectionProfile.serverName === connectionProfile.serverName) { + if (refreshStatus) { + try { + 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); } - - - return dataBaseMigrations; + this.context.globalState.update(this.mementoToken, validMigrations); + return result; } public static saveMigration( @@ -41,7 +57,7 @@ export class MigrationLocalStorage { targetMI: SqlManagedInstance, azureAccount: azdata.Account, subscription: azureResource.AzureResourceSubscription, - controller: SqlMigrationController): void { + controller: SqlMigrationService): void { try { const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || []; migrationMementos.push({ @@ -69,5 +85,5 @@ export interface MigrationContext { targetManagedInstance: SqlManagedInstance, azureAccount: azdata.Account, subscription: azureResource.AzureResourceSubscription, - controller: SqlMigrationController + controller: SqlMigrationService } diff --git a/extensions/sql-migration/src/models/product.ts b/extensions/sql-migration/src/models/product.ts index e9189e002b..94bfdb9925 100644 --- a/extensions/sql-migration/src/models/product.ts +++ b/extensions/sql-migration/src/models/product.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * 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'; const localize = nls.loadMessageBundle(); @@ -23,11 +24,11 @@ export interface Checks { export interface Product extends MigrationProduct { readonly name: string; readonly learnMoreLink?: string; - readonly icon?: string; + readonly icon?: IconPath; } 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) { } diff --git a/extensions/sql-migration/src/models/stateMachine.ts b/extensions/sql-migration/src/models/stateMachine.ts index e9091f69c4..c252f55a46 100644 --- a/extensions/sql-migration/src/models/stateMachine.ts +++ b/extensions/sql-migration/src/models/stateMachine.ts @@ -8,7 +8,7 @@ import { azureResource } from 'azureResource'; import * as azurecore from 'azurecore'; import * as vscode from 'vscode'; 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 * as constants from '../constants/strings'; import { MigrationLocalStorage } from './migrationLocalStorage'; @@ -96,8 +96,8 @@ export class MigrationStateModel implements Model, vscode.Disposable { public _refreshNetworkShareLocation!: azureResource.BlobContainer[]; public _targetDatabaseNames!: string[]; - public _migrationController!: SqlMigrationController; - public _migrationControllers!: SqlMigrationController[]; + public _sqlMigrationService!: SqlMigrationService; + public _sqlMigrationServices!: SqlMigrationService[]; public _nodeNames!: string[]; private _stateChangeEventEmitter = new vscode.EventEmitter(); @@ -426,39 +426,39 @@ export class MigrationStateModel implements Model, vscode.Disposable { } - public async getMigrationControllerValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance): Promise { - let migrationControllerValues: azdata.CategoryValue[] = []; + public async getSqlMigrationServiceValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance): Promise { + let sqlMigrationServiceValues: azdata.CategoryValue[] = []; try { - this._migrationControllers = await getMigrationControllers(this._azureAccount, subscription, managedInstance.resourceGroup!, managedInstance.location); - this._migrationControllers.forEach((migrationController) => { - migrationControllerValues.push({ - name: migrationController.id, - displayName: `${migrationController.name}` + this._sqlMigrationServices = await getSqlMigrationServices(this._azureAccount, subscription, managedInstance.location); + this._sqlMigrationServices.forEach((sqlMigrationService) => { + sqlMigrationServiceValues.push({ + name: sqlMigrationService.id, + displayName: `${sqlMigrationService.name}` }); }); - if (migrationControllerValues.length === 0) { - migrationControllerValues = [ + if (sqlMigrationServiceValues.length === 0) { + sqlMigrationServiceValues = [ { - displayName: constants.MIGRATION_CONTROLLER_NOT_FOUND_ERROR, + displayName: constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR, name: '' } ]; } } catch (e) { console.log(e); - migrationControllerValues = [ + sqlMigrationServiceValues = [ { - displayName: constants.MIGRATION_CONTROLLER_NOT_FOUND_ERROR, + displayName: constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR, name: '' } ]; } - return migrationControllerValues; + return sqlMigrationServiceValues; } - public getMigrationController(index: number): SqlMigrationController { - return this._migrationControllers[index]; + public getMigrationService(index: number): SqlMigrationService { + return this._sqlMigrationServices[index]; } public async startMigration() { @@ -473,41 +473,41 @@ export class MigrationStateModel implements Model, vscode.Disposable { const connectionPassword = await azdata.connection.getCredentials(this.sourceConnectionId); const requestBody: StartDatabaseMigrationRequest = { - location: this._migrationController?.properties.location!, + location: this._sqlMigrationService?.properties.location!, properties: { - SourceDatabaseName: '', - MigrationController: this._migrationController?.id!, - BackupConfiguration: { - TargetLocation: { - StorageAccountResourceId: this._databaseBackup.storageAccount.id, - AccountKey: this._databaseBackup.storageKey, + sourceDatabaseName: '', + migrationService: this._sqlMigrationService?.id!, + backupConfiguration: { + targetLocation: { + storageAccountResourceId: this._databaseBackup.storageAccount.id, + accountKey: this._databaseBackup.storageKey, }, - SourceLocation: { - FileShare: { - Path: '', - Username: this._databaseBackup.windowsUser, - Password: this._databaseBackup.password, + sourceLocation: { + fileShare: { + path: '', + username: this._databaseBackup.windowsUser, + password: this._databaseBackup.password, } }, }, - SourceSqlConnection: { - DataSource: currentConnection?.serverName!, - Username: currentConnection?.userName!, - Password: connectionPassword.password + sourceSqlConnection: { + dataSource: currentConnection?.serverName!, + username: currentConnection?.userName!, + password: connectionPassword.password }, - Scope: this._targetServerInstance.id + scope: this._targetServerInstance.id } }; this._migrationDbs.forEach(async (db, index) => { - requestBody.properties.SourceDatabaseName = db; + requestBody.properties.sourceDatabaseName = db; 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( this._azureAccount, this._targetSubscription, - this._migrationController?.properties.location!, + this._sqlMigrationService?.properties.location!, this._targetServerInstance, this._targetDatabaseNames[index], requestBody @@ -519,9 +519,9 @@ export class MigrationStateModel implements Model, vscode.Disposable { this._targetServerInstance, this._azureAccount, 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) { console.log(e); diff --git a/extensions/sql-migration/src/wizard/accountsSelectionPage.ts b/extensions/sql-migration/src/wizard/accountsSelectionPage.ts index e6131585fc..56007de0e0 100644 --- a/extensions/sql-migration/src/wizard/accountsSelectionPage.ts +++ b/extensions/sql-migration/src/wizard/accountsSelectionPage.ts @@ -47,6 +47,15 @@ export class AccountsSelectionPage extends MigrationWizardPage { }; 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; }).component(); @@ -69,7 +78,7 @@ export class AccountsSelectionPage extends MigrationWizardPage { this.migrationStateModel._subscriptions = undefined!; this.migrationStateModel._targetSubscription = undefined!; this.migrationStateModel._databaseBackup.subscription = undefined!; - + this._azureAccountsDropdown.validate(); } }); @@ -83,6 +92,9 @@ export class AccountsSelectionPage extends MigrationWizardPage { linkAccountButton.onDidClick(async (event) => { await vscode.commands.executeCommand('workbench.actions.modal.linkedAccount'); await this.populateAzureAccountsDropdown(); + this.wizard.message = { + text: '' + }; }); const flexContainer = view.modelBuilder.flexContainer() @@ -155,6 +167,15 @@ export class AccountsSelectionPage extends MigrationWizardPage { } public async onPageEnter(): Promise { + 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 { diff --git a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts index b0d7ea3e81..3762b09325 100644 --- a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts +++ b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts @@ -7,15 +7,15 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { MigrationWizardPage } from '../models/migrationWizardPage'; 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 { 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'; export class IntergrationRuntimePage extends MigrationWizardPage { - private migrationControllerDropdown!: azdata.DropDownComponent; + private migrationServiceDropdown!: azdata.DropDownComponent; private _view!: azdata.ModelView; private _form!: azdata.FormBuilder; private _statusLoadingComponent!: azdata.LoadingComponent; @@ -28,13 +28,13 @@ export class IntergrationRuntimePage extends MigrationWizardPage { protected async registerContent(view: azdata.ModelView): Promise { this._view = view; - const createNewController = view.modelBuilder.hyperlink().withProps({ + const createNewMigrationService = view.modelBuilder.hyperlink().withProps({ label: constants.CREATE_NEW, url: '' }).component(); - createNewController.onDidClick((e) => { - const dialog = new CreateMigrationControllerDialog(this.migrationStateModel, this); + createNewMigrationService.onDidClick((e) => { + const dialog = new CreateSqlMigrationServiceDialog(this.migrationStateModel, this); dialog.initialize(); }); @@ -47,10 +47,10 @@ export class IntergrationRuntimePage extends MigrationWizardPage { .withFormItems( [ { - component: this.migrationControllerDropdownsContainer() + component: this.migrationServiceDropdownContainer() }, { - component: createNewController + component: createNewMigrationService }, { component: this._statusLoadingComponent @@ -62,7 +62,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage { } public async onPageEnter(): Promise { - this.populateMigrationController(); + this.populateMigrationService(); this.wizard.registerNavigationValidator((pageChangeInfo) => { if (pageChangeInfo.newPage < pageChangeInfo.lastPage) { this.wizard.message = { @@ -70,18 +70,18 @@ export class IntergrationRuntimePage extends MigrationWizardPage { }; return true; } - const state = this.migrationStateModel._migrationController.properties.integrationRuntimeState; - if (!this.migrationStateModel._migrationController) { + const state = this.migrationStateModel._sqlMigrationService.properties.integrationRuntimeState; + if (!this.migrationStateModel._sqlMigrationService) { this.wizard.message = { level: azdata.window.MessageLevel.Error, - text: constants.INVALID_CONTROLLER_ERROR + text: constants.INVALID_SERVICE_ERROR }; return false; } if (state !== 'Online') { this.wizard.message = { level: azdata.window.MessageLevel.Error, - text: constants.CONTROLLER_OFFLINE_ERROR + text: constants.SERVICE_OFFLINE_ERROR }; return false; } else { @@ -102,7 +102,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage { protected async handleStateChange(e: StateChangeEvent): Promise { } - private migrationControllerDropdownsContainer(): azdata.FlexContainer { + private migrationServiceDropdownContainer(): azdata.FlexContainer { const descriptionText = this._view.modelBuilder.text().withProps({ value: constants.IR_PAGE_DESCRIPTION, links: [ @@ -117,23 +117,23 @@ export class IntergrationRuntimePage extends MigrationWizardPage { value: constants.IR_PAGE_NOTE }).component(); - const migrationControllerDropdownLabel = this._view.modelBuilder.text().withProps({ - value: constants.SELECT_A_MIGRATION_CONTROLLER + const migrationServcieDropdownLabel = this._view.modelBuilder.text().withProps({ + value: constants.SELECT_A_SQL_MIGRATION_SERVICE }).component(); - this.migrationControllerDropdown = this._view.modelBuilder.dropDown().withProps({ + this.migrationServiceDropdown = this._view.modelBuilder.dropDown().withProps({ required: true, width: WIZARD_INPUT_COMPONENT_WIDTH }).component(); - this.migrationControllerDropdown.onValueChanged(async (value) => { + this.migrationServiceDropdown.onValueChanged(async (value) => { if (value.selected) { this.wizard.message = { text: '' }; - this.migrationStateModel._migrationController = this.migrationStateModel.getMigrationController(value.index); - if (value !== constants.MIGRATION_CONTROLLER_NOT_FOUND_ERROR) { - await this.loadControllerStatus(); + this.migrationStateModel._sqlMigrationService = this.migrationStateModel.getMigrationService(value.index); + if (value !== constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR) { + await this.loadMigrationServiceStatus(); } } }); @@ -141,73 +141,77 @@ export class IntergrationRuntimePage extends MigrationWizardPage { const flexContainer = this._view.modelBuilder.flexContainer().withItems([ descriptionText, noteText, - migrationControllerDropdownLabel, - this.migrationControllerDropdown + migrationServcieDropdownLabel, + this.migrationServiceDropdown ]).withLayout({ flexFlow: 'column' }).component(); return flexContainer; } - public async populateMigrationController(): Promise { - this.migrationControllerDropdown.loading = true; + public async populateMigrationService(sqlMigrationService?: SqlMigrationService, serviceNodes?: string[]): Promise { + this.migrationServiceDropdown.loading = true; + if (sqlMigrationService && serviceNodes) { + this.migrationStateModel._sqlMigrationService = sqlMigrationService; + this.migrationStateModel._nodeNames = serviceNodes; + } try { - this.migrationControllerDropdown.values = await this.migrationStateModel.getMigrationControllerValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._targetServerInstance); - if (this.migrationStateModel._migrationController) { - this.migrationControllerDropdown.value = { - name: this.migrationStateModel._migrationController.id, - displayName: this.migrationStateModel._migrationController.name + this.migrationServiceDropdown.values = await this.migrationStateModel.getSqlMigrationServiceValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._targetServerInstance); + if (this.migrationStateModel._sqlMigrationService) { + this.migrationServiceDropdown.value = { + name: this.migrationStateModel._sqlMigrationService.id, + displayName: this.migrationStateModel._sqlMigrationService.name }; } else { - this.migrationStateModel._migrationController = this.migrationStateModel.getMigrationController(0); + this.migrationStateModel._sqlMigrationService = this.migrationStateModel.getMigrationService(0); } } catch (error) { console.log(error); } finally { - this.migrationControllerDropdown.loading = false; + this.migrationServiceDropdown.loading = false; } } - private async loadControllerStatus(): Promise { + private async loadMigrationServiceStatus(): Promise { this._statusLoadingComponent.loading = true; try { this._migrationDetailsContainer.clearItems(); - if (this.migrationStateModel._migrationController) { - const controller = await getMigrationController( + if (this.migrationStateModel._sqlMigrationService) { + const migrationService = await getSqlMigrationService( this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription, - this.migrationStateModel._migrationController.properties.resourceGroup, - this.migrationStateModel._migrationController.properties.location, - this.migrationStateModel._migrationController.name); - this.migrationStateModel._migrationController = controller; - const controllerMonitoringStatus = await getMigrationControllerMonitoringData( + this.migrationStateModel._sqlMigrationService.properties.resourceGroup, + this.migrationStateModel._sqlMigrationService.properties.location, + this.migrationStateModel._sqlMigrationService.name); + this.migrationStateModel._sqlMigrationService = migrationService; + const migrationServiceMonitoringStatus = await getSqlMigrationServiceMonitoringData( this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription, - this.migrationStateModel._migrationController.properties.resourceGroup, - this.migrationStateModel._migrationController.properties.location, - this.migrationStateModel._migrationController!.name); - this.migrationStateModel._nodeNames = controllerMonitoringStatus.nodes.map((node) => { + this.migrationStateModel._sqlMigrationService.properties.resourceGroup, + this.migrationStateModel._sqlMigrationService.properties.location, + this.migrationStateModel._sqlMigrationService!.name); + this.migrationStateModel._nodeNames = migrationServiceMonitoringStatus.nodes.map((node) => { return node.nodeName; }); - const migrationControllerAuthKeys = await getMigrationControllerAuthKeys( + const migrationServiceAuthKeys = await getSqlMigrationServiceAuthKeys( this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription, - this.migrationStateModel._migrationController.properties.resourceGroup, - this.migrationStateModel._migrationController.properties.location, - this.migrationStateModel._migrationController!.name + this.migrationStateModel._sqlMigrationService.properties.resourceGroup, + this.migrationStateModel._sqlMigrationService.properties.location, + this.migrationStateModel._sqlMigrationService!.name ); - const migrationControllerTitle = this._view.modelBuilder.text().withProps({ - value: constants.CONTROLLER_DETAILS_HEADER(controller.name), + const migrationServiceTitle = this._view.modelBuilder.text().withProps({ + value: constants.SQL_MIGRATION_SERVICE_DETAILS_HEADER(migrationService.name), CSSStyles: { 'font-weight': 'bold' } }).component(); const connectionStatusLabel = this._view.modelBuilder.text().withProps({ - value: constants.CONTROLLER_CONNECTION_STATUS, + value: constants.SERVICE_CONNECTION_STATUS, CSSStyles: { 'font-weight': 'bold', 'width': '150px' @@ -242,32 +246,32 @@ export class IntergrationRuntimePage extends MigrationWizardPage { refreshStatus.onDidClick(async (e) => { connectionStatusLoader.loading = true; - const controller = await getMigrationController( + const migrationService = await getSqlMigrationService( this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription, - this.migrationStateModel._migrationController.properties.resourceGroup, - this.migrationStateModel._migrationController.properties.location, - this.migrationStateModel._migrationController.name); - this.migrationStateModel._migrationController = controller; - const controllerMonitoringStatus = await getMigrationControllerMonitoringData( + this.migrationStateModel._sqlMigrationService.properties.resourceGroup, + this.migrationStateModel._sqlMigrationService.properties.location, + this.migrationStateModel._sqlMigrationService.name); + this.migrationStateModel._sqlMigrationService = migrationService; + const migrationServiceMonitoringStatus = await getSqlMigrationServiceMonitoringData( this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription, - this.migrationStateModel._migrationController.properties.resourceGroup, - this.migrationStateModel._migrationController.properties.location, - this.migrationStateModel._migrationController!.name); - this.migrationStateModel._nodeNames = controllerMonitoringStatus.nodes.map((node) => { + this.migrationStateModel._sqlMigrationService.properties.resourceGroup, + this.migrationStateModel._sqlMigrationService.properties.location, + this.migrationStateModel._sqlMigrationService!.name); + this.migrationStateModel._nodeNames = migrationServiceMonitoringStatus.nodes.map((node) => { return node.nodeName; }); - const state = controller.properties.integrationRuntimeState; + const state = migrationService.properties.integrationRuntimeState; if (state === 'Online') { connectionStatus.updateProperties({ - 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' }); } else { connectionStatus.updateProperties({ - text: constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name), + text: constants.SERVICE_NOT_READY(this.migrationStateModel._sqlMigrationService!.name), style: 'error' }); } @@ -275,16 +279,16 @@ export class IntergrationRuntimePage extends MigrationWizardPage { connectionStatusLoader.loading = false; }); - if (controller) { - const state = controller.properties.integrationRuntimeState; + const state = migrationService.properties.integrationRuntimeState; + if (migrationService) { if (state === 'Online') { connectionStatus.updateProperties({ - 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' }); } else { connectionStatus.updateProperties({ - text: constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name), + text: constants.SERVICE_NOT_READY(this.migrationStateModel._sqlMigrationService!.name), style: 'error' }); } @@ -297,7 +301,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage { } }).component(); - const migrationControllerAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({ + const migrationServiceAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({ columns: [ { displayName: constants.NAME, @@ -341,8 +345,8 @@ export class IntergrationRuntimePage extends MigrationWizardPage { }).component(); copyKey1Button.onDidClick((e) => { - vscode.env.clipboard.writeText(migrationControllerAuthKeyTable.dataValues![0][1].value); - vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); + vscode.env.clipboard.writeText(migrationServiceAuthKeyTable.dataValues![0][1].value); + vscode.window.showInformationMessage(constants.SERVICE_KEY_COPIED_HELP); }); const copyKey2Button = this._view.modelBuilder.button().withProps({ @@ -350,8 +354,8 @@ export class IntergrationRuntimePage extends MigrationWizardPage { }).component(); copyKey2Button.onDidClick((e) => { - vscode.env.clipboard.writeText(migrationControllerAuthKeyTable.dataValues![1][1].value); - vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); + vscode.env.clipboard.writeText(migrationServiceAuthKeyTable.dataValues![1][1].value); + vscode.window.showInformationMessage(constants.SERVICE_KEY_COPIED_HELP); }); const refreshKey1Button = this._view.modelBuilder.button().withProps({ @@ -368,15 +372,14 @@ export class IntergrationRuntimePage extends MigrationWizardPage { refreshKey2Button.onDidClick((e) => {//TODO: add refresh logic }); - - migrationControllerAuthKeyTable.updateProperties({ + migrationServiceAuthKeyTable.updateProperties({ dataValues: [ [ { - value: constants.CONTROLLER_KEY1_LABEL + value: constants.SERVICE_KEY1_LABEL }, { - value: migrationControllerAuthKeys.authKey1 + value: migrationServiceAuthKeys.authKey1 }, { 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 @@ -404,14 +407,14 @@ export class IntergrationRuntimePage extends MigrationWizardPage { this._migrationDetailsContainer.addItems( [ - migrationControllerTitle, + migrationServiceTitle, createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name), - createInformationRow(this._view, constants.RESOURCE_GROUP, controller.properties.resourceGroup), - createInformationRow(this._view, constants.LOCATION, controller.properties.location), + createInformationRow(this._view, constants.RESOURCE_GROUP, migrationService.properties.resourceGroup), + createInformationRow(this._view, constants.LOCATION, migrationService.properties.location), connectionLabelContainer, connectionStatusLoader, authenticationKeysLabel, - migrationControllerAuthKeyTable + migrationServiceAuthKeyTable ] ); } diff --git a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts index 088628a2f5..e345aa010c 100644 --- a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts +++ b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts @@ -4,18 +4,32 @@ *--------------------------------------------------------------------------------------------*/ import * as azdata from 'azdata'; -import * as path from 'path'; import { MigrationWizardPage } from '../models/migrationWizardPage'; import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine'; -import { Product, ProductLookupTable } from '../models/product'; +import { Product } from '../models/product'; import { AssessmentResultsDialog } from '../dialog/assessmentResults/assessmentResultsDialog'; import * as constants from '../constants/strings'; import * as vscode from 'vscode'; import { EOL } from 'os'; +import { IconPathHelper } from '../constants/iconPathHelper'; // import { SqlMigrationService } from '../../../../extensions/mssql/src/sqlMigration/sqlMigrationService'; 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. constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) { super(wizard, azdata.window.createWizardPage(constants.SKU_RECOMMENDATION_PAGE_TITLE), migrationStateModel); @@ -53,7 +67,7 @@ export class SKURecommendationPage extends MigrationWizardPage { if (e.selected) { this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index); this.migrationStateModel._targetServerInstance = undefined!; - this.migrationStateModel._migrationController = undefined!; + this.migrationStateModel._sqlMigrationService = undefined!; this.populateResourceInstanceDropdown(); } }); @@ -66,7 +80,7 @@ export class SKURecommendationPage extends MigrationWizardPage { if (e.selected && e.selected !== constants.NO_MANAGED_INSTANCE_FOUND && e.selected !== constants.NO_VIRTUAL_MACHINE_FOUND) { - this.migrationStateModel._migrationControllers = undefined!; + this.migrationStateModel._sqlMigrationServices = undefined!; if (this._rbg.selectedCardId === 'AzureSQLVM') { this.migrationStateModel._targetServerInstance = this.migrationStateModel.getVirtualMachine(e.index); } else { @@ -179,7 +193,7 @@ export class SKURecommendationPage extends MigrationWizardPage { } private constructTargets(): void { - const products: Product[] = Object.values(ProductLookupTable); + const products: Product[] = this.supportedProducts; this._rbg = this._view!.modelBuilder.radioCardGroup().withProperties({ cards: [], @@ -191,7 +205,6 @@ export class SKURecommendationPage extends MigrationWizardPage { }).component(); products.forEach((product) => { - const imagePath = path.resolve(this.migrationStateModel.getExtensionPath(), 'media', product.icon ?? 'ads.svg'); let dbCount = 0; if (product.type === 'AzureSQLVM') { dbCount = this._dbCount; @@ -238,7 +251,7 @@ export class SKURecommendationPage extends MigrationWizardPage { this._rbg.cards.push({ id: product.type, - icon: imagePath, + icon: product.icon, descriptions }); }); @@ -248,7 +261,6 @@ export class SKURecommendationPage extends MigrationWizardPage { this._rbg.onLinkClick(async (value) => { //check which card is being selected, and open correct dialog based on link - console.log(value); if (value.description.linkDisplayValue === 'View/Change') { if (value.cardId === 'AzureSQLVM') { await vmDialog.openDialog(); diff --git a/extensions/sql-migration/src/wizard/summaryPage.ts b/extensions/sql-migration/src/wizard/summaryPage.ts index 0f0dde87ee..00458364fe 100644 --- a/extensions/sql-migration/src/wizard/summaryPage.ts +++ b/extensions/sql-migration/src/wizard/summaryPage.ts @@ -46,7 +46,7 @@ export class SummaryPage extends MigrationWizardPage { createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_PAGE_TITLE), this.createNetworkContainerRows(), 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(', ')), ] diff --git a/extensions/sql-migration/src/wizard/tempTargetSelectionPage.ts b/extensions/sql-migration/src/wizard/tempTargetSelectionPage.ts index 677a0a6693..e9f3fa8f6d 100644 --- a/extensions/sql-migration/src/wizard/tempTargetSelectionPage.ts +++ b/extensions/sql-migration/src/wizard/tempTargetSelectionPage.ts @@ -33,7 +33,7 @@ export class TempTargetSelectionPage extends MigrationWizardPage { if (e.selected) { this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index); this.migrationStateModel._targetServerInstance = undefined!; - this.migrationStateModel._migrationController = undefined!; + this.migrationStateModel._sqlMigrationService = undefined!; this.populateManagedInstanceDropdown(); } }); @@ -48,7 +48,7 @@ export class TempTargetSelectionPage extends MigrationWizardPage { }).component(); this._managedInstanceDropdown.onValueChanged((e) => { if (e.selected) { - this.migrationStateModel._migrationControllers = undefined!; + this.migrationStateModel._sqlMigrationServices = undefined!; this.migrationStateModel._targetServerInstance = this.migrationStateModel.getManagedInstance(e.index); } });