From d21ee4dc9e24fce54ccb546b1aee8953ea08014e Mon Sep 17 00:00:00 2001 From: Aasim Khan Date: Mon, 22 Feb 2021 19:00:39 -0800 Subject: [PATCH] Added fetch logic for controllers (#14380) * . Added fetch logic for controllers (no need to create a new one everytime) . Fixed retention logic * Fix field reloading logic. Fixed localized string Removing hardcoded colors --- extensions/sql-migration/src/api/azure.ts | 4 +- .../createMigrationControllerDialog.ts | 26 +- .../src/models/migrationLocalStorage.ts | 16 +- .../sql-migration/src/models/stateMachine.ts | 87 ++--- .../sql-migration/src/models/strings.ts | 7 +- .../src/wizard/accountsSelectionPage.ts | 7 +- .../src/wizard/databaseBackupPage.ts | 175 +++++---- .../src/wizard/integrationRuntimePage.ts | 368 +++++++++++++++--- .../sql-migration/src/wizard/summaryPage.ts | 98 ++--- .../src/wizard/tempTargetSelectionPage.ts | 44 +-- .../src/wizard/wizardController.ts | 48 ++- 11 files changed, 570 insertions(+), 310 deletions(-) diff --git a/extensions/sql-migration/src/api/azure.ts b/extensions/sql-migration/src/api/azure.ts index d249f3d72b..b5903e027a 100644 --- a/extensions/sql-migration/src/api/azure.ts +++ b/extensions/sql-migration/src/api/azure.ts @@ -96,12 +96,12 @@ export async function getMigrationController(account: azdata.Account, subscripti export async function getMigrationControllers(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string): Promise { const api = await getAzureCoreAPI(); const host = `https://${regionName}.management.azure.com`; - const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/Controllers?api-version=2020-09-01-preview`; + const path = `/subscriptions/${subscription.id}/providers/Microsoft.DataMigration/Controllers?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()); } - return response.response.data; + return response.response.data.value; } export async function createMigrationController(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, controllerName: string): Promise { diff --git a/extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts b/extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts index 708bc37b10..d5a269cdff 100644 --- a/extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts +++ b/extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts @@ -69,7 +69,7 @@ export class CreateMigrationControllerDialog { } try { - const createdController = await createMigrationController(this.migrationStateModel.azureAccount, subscription, resourceGroup, region, controllerName!); + const createdController = await createMigrationController(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, controllerName!); if (createdController.error) { this.setDialogMessage(`${createdController.error.code} : ${createdController.error.message}`); this._statusLoadingComponent.loading = false; @@ -79,7 +79,7 @@ export class CreateMigrationControllerDialog { this._dialogObject.message = { text: '' }; - this.migrationStateModel.migrationController = createdController; + this.migrationStateModel._migrationController = createdController; await this.refreshAuthTable(); await this.refreshStatus(); this._setupContainer.display = 'inline'; @@ -130,7 +130,7 @@ export class CreateMigrationControllerDialog { this._dialogObject.okButton.enabled = false; azdata.window.openDialog(this._dialogObject); this._dialogObject.cancelButton.onClick((e) => { - this.migrationStateModel.migrationController = undefined!; + this.migrationStateModel._migrationController = undefined!; }); this._dialogObject.okButton.onClick((e) => { this.irPage.populateMigrationController(); @@ -242,7 +242,7 @@ export class CreateMigrationControllerDialog { private async populateResourceGroups(): Promise { this.migrationControllerResourceGroupDropdown.loading = true; let subscription = this.migrationStateModel._targetSubscription; - const resourceGroups = await getResourceGroups(this.migrationStateModel.azureAccount, subscription); + const resourceGroups = await getResourceGroups(this.migrationStateModel._azureAccount, subscription); let resourceGroupDropdownValues: azdata.CategoryValue[] = []; if (resourceGroups && resourceGroups.length > 0) { resourceGroups.forEach((resourceGroup) => { @@ -392,8 +392,8 @@ export class CreateMigrationControllerDialog { 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); + 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) => { return node.nodeName; }); @@ -402,14 +402,14 @@ export class CreateMigrationControllerDialog { if (state === 'Online') { this._connectionStatus.updateProperties({ - text: constants.CONTROLLER_READY(this.migrationStateModel.migrationController!.name, this.migrationStateModel._nodeNames.join(', ')), + text: constants.CONTROLLER_READY(this.migrationStateModel._migrationController!.name, this.migrationStateModel._nodeNames.join(', ')), style: 'success' }); this._dialogObject.okButton.enabled = true; } else { - this._connectionStatus.text = constants.CONTROLLER_NOT_READY(this.migrationStateModel.migrationController!.name); + this._connectionStatus.text = constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name); this._connectionStatus.updateProperties({ - text: constants.CONTROLLER_NOT_READY(this.migrationStateModel.migrationController!.name), + text: constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name), style: 'warning' }); this._dialogObject.okButton.enabled = false; @@ -421,7 +421,7 @@ export class CreateMigrationControllerDialog { 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 keys = await getMigrationControllerAuthKeys(this.migrationStateModel._azureAccount, subscription, resourceGroup, region, this.migrationStateModel._migrationController!.name); this._copyKey1Button = this._view.modelBuilder.button().withProps({ iconPath: IconPathHelper.copy @@ -445,16 +445,14 @@ export class CreateMigrationControllerDialog { iconPath: IconPathHelper.refresh }).component(); - this._refreshKey1Button.onDidClick((e) => { - this.refreshAuthTable(); + this._refreshKey1Button.onDidClick((e) => {//TODO: add refresh logic }); this._refreshKey2Button = this._view.modelBuilder.button().withProps({ iconPath: IconPathHelper.refresh }).component(); - this._refreshKey2Button.onDidClick((e) => { - this.refreshAuthTable(); + this._refreshKey2Button.onDidClick((e) => { //TODO: add refresh logic }); this.migrationControllerAuthKeyTable.updateProperties({ diff --git a/extensions/sql-migration/src/models/migrationLocalStorage.ts b/extensions/sql-migration/src/models/migrationLocalStorage.ts index 19db37a846..ce85361b32 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, SqlManagedInstance } from '../api/azure'; +import { DatabaseMigration, MigrationController, SqlManagedInstance } from '../api/azure'; import * as azdata from 'azdata'; @@ -35,7 +35,13 @@ export class MigrationLocalStorage { return dataBaseMigrations; } - public static saveMigration(connectionProfile: azdata.connection.ConnectionProfile, migrationContext: DatabaseMigration, targetMI: SqlManagedInstance, azureAccount: azdata.Account, subscription: azureResource.AzureResourceSubscription): void { + public static saveMigration( + connectionProfile: azdata.connection.ConnectionProfile, + migrationContext: DatabaseMigration, + targetMI: SqlManagedInstance, + azureAccount: azdata.Account, + subscription: azureResource.AzureResourceSubscription, + controller: MigrationController): void { try { const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || []; migrationMementos.push({ @@ -43,7 +49,8 @@ export class MigrationLocalStorage { migrationContext: migrationContext, targetManagedInstance: targetMI, subscription: subscription, - azureAccount: azureAccount + azureAccount: azureAccount, + controller: controller }); this.context.globalState.update(this.mementoToken, migrationMementos); } catch (e) { @@ -61,5 +68,6 @@ export interface MigrationContext { migrationContext: DatabaseMigration, targetManagedInstance: SqlManagedInstance, azureAccount: azdata.Account, - subscription: azureResource.AzureResourceSubscription + subscription: azureResource.AzureResourceSubscription, + controller: MigrationController } diff --git a/extensions/sql-migration/src/models/stateMachine.ts b/extensions/sql-migration/src/models/stateMachine.ts index 353d000b10..d8a00973fa 100644 --- a/extensions/sql-migration/src/models/stateMachine.ts +++ b/extensions/sql-migration/src/models/stateMachine.ts @@ -65,8 +65,8 @@ export interface Model { readonly currentState: State; gatheringInformationError: string | undefined; skuRecommendations: SKURecommendations | undefined; - azureAccount: azdata.Account | undefined; - databaseBackup: DatabaseBackupModel | undefined; + _azureAccount: azdata.Account | undefined; + _databaseBackup: DatabaseBackupModel | undefined; } export interface StateChangeEvent { @@ -75,22 +75,22 @@ export interface StateChangeEvent { } export class MigrationStateModel implements Model, vscode.Disposable { - public azureAccounts!: azdata.Account[]; - public azureAccount!: azdata.Account; + public _azureAccounts!: azdata.Account[]; + public _azureAccount!: azdata.Account; - public subscriptions!: azureResource.AzureResourceSubscription[]; + public _subscriptions!: azureResource.AzureResourceSubscription[]; public _targetSubscription!: azureResource.AzureResourceSubscription; public _targetManagedInstances!: SqlManagedInstance[]; public _targetManagedInstance!: SqlManagedInstance; - public databaseBackup!: DatabaseBackupModel; + public _databaseBackup!: DatabaseBackupModel; public _storageAccounts!: StorageAccount[]; public _fileShares!: azureResource.FileShare[]; public _blobContainers!: azureResource.BlobContainer[]; - public migrationController!: MigrationController; - public migrationControllers!: MigrationController[]; + public _migrationController!: MigrationController; + public _migrationControllers!: MigrationController[]; public _nodeNames!: string[]; private _stateChangeEventEmitter = new vscode.EventEmitter(); @@ -107,7 +107,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { public readonly migrationService: mssql.ISqlMigrationService ) { this._currentState = State.INIT; - this.databaseBackup = {} as DatabaseBackupModel; + this._databaseBackup = {} as DatabaseBackupModel; } public get sourceConnectionId(): string { @@ -165,14 +165,14 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getAccountValues(): Promise { let accountValues: azdata.CategoryValue[] = []; try { - this.azureAccounts = await azdata.accounts.getAllAccounts(); - if (this.azureAccounts.length === 0) { + this._azureAccounts = await azdata.accounts.getAllAccounts(); + if (this._azureAccounts.length === 0) { accountValues = [{ displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR, name: '' }]; } - accountValues = this.azureAccounts.map((account): azdata.CategoryValue => { + accountValues = this._azureAccounts.map((account): azdata.CategoryValue => { return { displayName: account.displayInfo.displayName, name: account.displayInfo.userId @@ -189,16 +189,16 @@ export class MigrationStateModel implements Model, vscode.Disposable { } public getAccount(index: number): azdata.Account { - return this.azureAccounts[index]; + return this._azureAccounts[index]; } public async getSubscriptionsDropdownValues(): Promise { let subscriptionsValues: azdata.CategoryValue[] = []; try { - if (!this.subscriptions) { - this.subscriptions = await getSubscriptions(this.azureAccount); + if (!this._subscriptions) { + this._subscriptions = await getSubscriptions(this._azureAccount); } - this.subscriptions.forEach((subscription) => { + this._subscriptions.forEach((subscription) => { subscriptionsValues.push({ name: subscription.id, displayName: `${subscription.name} - ${subscription.id}` @@ -227,15 +227,13 @@ export class MigrationStateModel implements Model, vscode.Disposable { } public getSubscription(index: number): azureResource.AzureResourceSubscription { - return this.subscriptions[index]; + return this._subscriptions[index]; } public async getManagedInstanceValues(subscription: azureResource.AzureResourceSubscription): Promise { let managedInstanceValues: azdata.CategoryValue[] = []; try { - if (!this._targetManagedInstances) { - this._targetManagedInstances = await getAvailableManagedInstanceProducts(this.azureAccount, subscription); - } + this._targetManagedInstances = await getAvailableManagedInstanceProducts(this._azureAccount, subscription); this._targetManagedInstances.forEach((managedInstance) => { managedInstanceValues.push({ name: managedInstance.id, @@ -270,9 +268,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getStorageAccountValues(subscription: azureResource.AzureResourceSubscription): Promise { let storageAccountValues: azdata.CategoryValue[] = []; try { - if (!this._storageAccounts) { - this._storageAccounts = await getAvailableStorageAccounts(this.azureAccount, subscription); - } + this._storageAccounts = await getAvailableStorageAccounts(this._azureAccount, subscription); this._storageAccounts.forEach((storageAccount) => { storageAccountValues.push({ name: storageAccount.id, @@ -307,9 +303,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getFileShareValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise { let fileShareValues: azdata.CategoryValue[] = []; try { - if (!this._fileShares) { - this._fileShares = await getFileShares(this.azureAccount, subscription, storageAccount); - } + this._fileShares = await getFileShares(this._azureAccount, subscription, storageAccount); this._fileShares.forEach((fileShare) => { fileShareValues.push({ name: fileShare.id, @@ -344,9 +338,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getBlobContainerValues(subscription: azureResource.AzureResourceSubscription, storageAccount: StorageAccount): Promise { let blobContainerValues: azdata.CategoryValue[] = []; try { - if (!this._blobContainers) { - this._blobContainers = await getBlobContainers(this.azureAccount, subscription, storageAccount); - } + this._blobContainers = await getBlobContainers(this._azureAccount, subscription, storageAccount); this._blobContainers.forEach((blobContainer) => { blobContainerValues.push({ name: blobContainer.id, @@ -382,10 +374,8 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async getMigrationControllerValues(subscription: azureResource.AzureResourceSubscription, managedInstance: SqlManagedInstance): Promise { let migrationControllerValues: azdata.CategoryValue[] = []; try { - if (!this.migrationControllers) { - this.migrationControllers = await getMigrationControllers(this.azureAccount, subscription, managedInstance.resourceGroup!, managedInstance.location); - } - this.migrationControllers.forEach((migrationController) => { + this._migrationControllers = await getMigrationControllers(this._azureAccount, subscription, managedInstance.resourceGroup!, managedInstance.location); + this._migrationControllers.forEach((migrationController) => { migrationControllerValues.push({ name: migrationController.id, displayName: `${migrationController.name}` @@ -413,7 +403,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { } public getMigrationController(index: number): MigrationController { - return this.migrationControllers[index]; + return this._migrationControllers[index]; } public async startMigration() { @@ -428,20 +418,20 @@ 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._migrationController?.properties.location!, properties: { SourceDatabaseName: currentConnection?.databaseName!, - MigrationController: this.migrationController?.id!, + MigrationController: this._migrationController?.id!, BackupConfiguration: { TargetLocation: { - StorageAccountResourceId: this.databaseBackup.storageAccount.id, - AccountKey: this.databaseBackup.storageKey, + StorageAccountResourceId: this._databaseBackup.storageAccount.id, + AccountKey: this._databaseBackup.storageKey, }, SourceLocation: { FileShare: { - Path: this.databaseBackup.networkShareLocation, - Username: this.databaseBackup.windowsUser, - Password: this.databaseBackup.password, + Path: this._databaseBackup.networkShareLocation, + Username: this._databaseBackup.windowsUser, + Password: this._databaseBackup.password, } }, }, @@ -455,17 +445,24 @@ export class MigrationStateModel implements Model, vscode.Disposable { }; const response = await startDatabaseMigration( - this.azureAccount, + this._azureAccount, this._targetSubscription, this._targetManagedInstance.resourceGroup!, - this.migrationController?.properties.location!, + this._migrationController?.properties.location!, this._targetManagedInstance.name, - this.migrationController?.name!, + this._migrationController?.name!, requestBody ); if (response.status === 201) { - MigrationLocalStorage.saveMigration(currentConnection!, response.databaseMigration, this._targetManagedInstance, this.azureAccount, this._targetSubscription); + MigrationLocalStorage.saveMigration( + currentConnection!, + response.databaseMigration, + this._targetManagedInstance, + this._azureAccount, + this._targetSubscription, + this._migrationController + ); } vscode.window.showInformationMessage(constants.MIGRATION_STARTED); diff --git a/extensions/sql-migration/src/models/strings.ts b/extensions/sql-migration/src/models/strings.ts index cfc007976a..4313be0269 100644 --- a/extensions/sql-migration/src/models/strings.ts +++ b/extensions/sql-migration/src/models/strings.ts @@ -98,6 +98,12 @@ export const DEFAULT_SETUP_BUTTON = localize('sql.migration.default.setup.button 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 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 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); +} // 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}"); @@ -128,7 +134,6 @@ export const INVALID_REGION_ERROR = localize('sql.migration.invalid.region.error 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 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 TARGET_SELECTION_PAGE_TITLE = localize('sql.migration.target.page.title', "Choose the target Azure SQL"); diff --git a/extensions/sql-migration/src/wizard/accountsSelectionPage.ts b/extensions/sql-migration/src/wizard/accountsSelectionPage.ts index db9d9ca253..9037bd9573 100644 --- a/extensions/sql-migration/src/wizard/accountsSelectionPage.ts +++ b/extensions/sql-migration/src/wizard/accountsSelectionPage.ts @@ -48,8 +48,10 @@ export class AccountsSelectionPage extends MigrationWizardPage { this._azureAccountsDropdown.onValueChanged(async (value) => { if (value.selected) { - this.migrationStateModel.azureAccount = this.migrationStateModel.getAccount(value.index); - this.migrationStateModel.subscriptions = undefined!; + this.migrationStateModel._azureAccount = this.migrationStateModel.getAccount(value.index); + this.migrationStateModel._subscriptions = undefined!; + this.migrationStateModel._targetSubscription = undefined!; + this.migrationStateModel._databaseBackup.subscription = undefined!; } }); @@ -82,7 +84,6 @@ export class AccountsSelectionPage extends MigrationWizardPage { this._azureAccountsDropdown.loading = true; try { this._azureAccountsDropdown.values = await this.migrationStateModel.getAccountValues(); - this.migrationStateModel.azureAccount = this.migrationStateModel.getAccount(0); } finally { this._azureAccountsDropdown.loading = false; } diff --git a/extensions/sql-migration/src/wizard/databaseBackupPage.ts b/extensions/sql-migration/src/wizard/databaseBackupPage.ts index 968380d32f..7903e9730f 100644 --- a/extensions/sql-migration/src/wizard/databaseBackupPage.ts +++ b/extensions/sql-migration/src/wizard/databaseBackupPage.ts @@ -128,8 +128,8 @@ export class DatabaseBackupPage extends MigrationWizardPage { }).component(); this._fileShareSubscriptionDropdown.onValueChanged(async (value) => { if (value.selected) { - this.migrationStateModel.databaseBackup.subscription = this.migrationStateModel.getSubscription(value.index); - this.migrationStateModel._storageAccounts = undefined!; + this.migrationStateModel._databaseBackup.subscription = this.migrationStateModel.getSubscription(value.index); + this.migrationStateModel._databaseBackup.storageAccount = undefined!; await this.loadFileShareStorageDropdown(); } }); @@ -145,8 +145,8 @@ export class DatabaseBackupPage extends MigrationWizardPage { }).component(); this._fileShareStorageAccountDropdown.onValueChanged(async (value) => { if (value.selected) { - this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index); - this.migrationStateModel._fileShares = undefined!; + this.migrationStateModel._databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index); + this.migrationStateModel._databaseBackup.fileShare = undefined!; await this.loadFileShareDropdown(); } }); @@ -162,7 +162,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { }).component(); this._fileShareFileShareDropdown.onValueChanged((value) => { if (value.selected) { - this.migrationStateModel.databaseBackup.fileShare = this.migrationStateModel.getFileShare(value.index); + this.migrationStateModel._databaseBackup.fileShare = this.migrationStateModel.getFileShare(value.index); } }); @@ -198,8 +198,8 @@ export class DatabaseBackupPage extends MigrationWizardPage { }).component(); this._blobContainerSubscriptionDropdown.onValueChanged(async (value) => { if (value.selected) { - this.migrationStateModel.databaseBackup.subscription = this.migrationStateModel.getSubscription(value.index); - this.migrationStateModel._storageAccounts = undefined!; + this.migrationStateModel._databaseBackup.subscription = this.migrationStateModel.getSubscription(value.index); + this.migrationStateModel._databaseBackup.storageAccount = undefined!; await this.loadblobStorageDropdown(); } }); @@ -215,8 +215,8 @@ export class DatabaseBackupPage extends MigrationWizardPage { }).component(); this._blobContainerStorageAccountDropdown.onValueChanged(async (value) => { if (value.selected) { - this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index); - this.migrationStateModel._blobContainers = undefined!; + this.migrationStateModel._databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index); + this.migrationStateModel._databaseBackup.blobContainer = undefined!; await this.loadBlobContainerDropdown(); } }); @@ -231,7 +231,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { }).component(); this._blobContainerBlobDropdown.onValueChanged((value) => { if (value.selected) { - this.migrationStateModel.databaseBackup.blobContainer = this.migrationStateModel.getBlobContainer(value.index); + this.migrationStateModel._databaseBackup.blobContainer = this.migrationStateModel.getBlobContainer(value.index); } }); @@ -272,7 +272,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { validationErrorMessage: constants.INVALID_NETWORK_SHARE_LOCATION }) .withValidation((component) => { - if (this.migrationStateModel.databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) { + if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) { if (component.value) { if (!/(?<=\\\\)[^\\]*/.test(component.value)) { return false; @@ -282,7 +282,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { return true; }).component(); this._networkShareLocationText.onTextChanged((value) => { - this.migrationStateModel.databaseBackup.networkShareLocation = value; + this.migrationStateModel._databaseBackup.networkShareLocation = value; }); const windowsUserAccountLabel = view.modelBuilder.text() @@ -297,7 +297,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { validationErrorMessage: constants.INVALID_USER_ACCOUNT }) .withValidation((component) => { - if (this.migrationStateModel.databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) { + if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) { if (component.value) { if (!/(?<=\\).*$/.test(component.value)) { return false; @@ -307,7 +307,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { return true; }).component(); this._windowsUserAccountText.onTextChanged((value) => { - this.migrationStateModel.databaseBackup.windowsUser = value; + this.migrationStateModel._databaseBackup.windowsUser = value; }); const passwordLabel = view.modelBuilder.text() @@ -322,7 +322,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { required: true }).component(); this._passwordText.onTextChanged((value) => { - this.migrationStateModel.databaseBackup.password = value; + this.migrationStateModel._databaseBackup.password = value; }); const azureAccountHelpText = view.modelBuilder.text() @@ -341,8 +341,8 @@ export class DatabaseBackupPage extends MigrationWizardPage { }).component(); this._networkShareContainerSubscriptionDropdown.onValueChanged(async (value) => { if (value.selected) { - this.migrationStateModel.databaseBackup.subscription = this.migrationStateModel.getSubscription(value.index); - this.migrationStateModel._storageAccounts = undefined!; + this.migrationStateModel._databaseBackup.subscription = this.migrationStateModel.getSubscription(value.index); + this.migrationStateModel._databaseBackup.storageAccount = undefined!; await this.loadNetworkShareStorageDropdown(); } }); @@ -358,7 +358,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { }).component(); this._networkShareContainerStorageAccountDropdown.onValueChanged((value) => { if (value.selected) { - this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index); + this.migrationStateModel._databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(value.index); } }); @@ -399,11 +399,11 @@ export class DatabaseBackupPage extends MigrationWizardPage { checked: true }).component(); - this.migrationStateModel.databaseBackup.migrationCutover = MigrationCutover.ONLINE; + this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.ONLINE; onlineButton.onDidChangeCheckedState((e) => { if (e) { - this.migrationStateModel.databaseBackup.migrationCutover = MigrationCutover.ONLINE; + this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.ONLINE; } }); @@ -414,7 +414,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { offlineButton.onDidChangeCheckedState((e) => { if (e) { - this.migrationStateModel.databaseBackup.migrationCutover = MigrationCutover.OFFLINE; + this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.OFFLINE; } }); @@ -443,7 +443,7 @@ export class DatabaseBackupPage extends MigrationWizardPage { const errors: string[] = []; - switch (this.migrationStateModel.databaseBackup.networkContainerType) { + switch (this.migrationStateModel._databaseBackup.networkContainerType) { case NetworkContainerType.NETWORK_SHARE: if ((this._networkShareContainerSubscriptionDropdown.value).displayName === constants.NO_SUBSCRIPTIONS_FOUND) { errors.push(constants.INVALID_SUBSCRIPTION_ERROR); @@ -488,15 +488,15 @@ export class DatabaseBackupPage extends MigrationWizardPage { } public async onPageLeave(): Promise { - this.migrationStateModel.databaseBackup.storageKey = (await getStorageAccountAccessKeys(this.migrationStateModel.azureAccount, this.migrationStateModel.databaseBackup.subscription, this.migrationStateModel.databaseBackup.storageAccount)).keyName1; - console.log(this.migrationStateModel.databaseBackup); + this.migrationStateModel._databaseBackup.storageKey = (await getStorageAccountAccessKeys(this.migrationStateModel._azureAccount, this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.storageAccount)).keyName1; + console.log(this.migrationStateModel._databaseBackup); } protected async handleStateChange(e: StateChangeEvent): Promise { } private toggleNetworkContainerFields(containerType: NetworkContainerType): void { - this.migrationStateModel.databaseBackup.networkContainerType = containerType; + this.migrationStateModel._databaseBackup.networkContainerType = containerType; this._fileShareContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.FILE_SHARE) ? 'inline' : 'none' }); this._blobContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.BLOB_CONTAINER) ? 'inline' : 'none' }); this._networkShareContainer.updateCssStyles({ 'display': (containerType === NetworkContainerType.NETWORK_SHARE) ? 'inline' : 'none' }); @@ -525,89 +525,92 @@ export class DatabaseBackupPage extends MigrationWizardPage { } private async getSubscriptionValues(): Promise { - this._networkShareContainerSubscriptionDropdown.loading = true; - this._fileShareSubscriptionDropdown.loading = true; - this._blobContainerSubscriptionDropdown.loading = true; - try { - this._fileShareSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues(); - this._networkShareContainerSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues(); - this._blobContainerSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues(); - this.migrationStateModel.databaseBackup.subscription = this.migrationStateModel.getSubscription(0); - } catch (error) { - this.migrationStateModel._storageAccounts = undefined!; - } finally { - await this.loadNetworkShareStorageDropdown(); - await this.loadFileShareStorageDropdown(); - await this.loadblobStorageDropdown(); - this._networkShareContainerSubscriptionDropdown.loading = false; - this._fileShareSubscriptionDropdown.loading = false; - this._blobContainerSubscriptionDropdown.loading = false; + if (!this.migrationStateModel._databaseBackup.subscription) { + this._networkShareContainerSubscriptionDropdown.loading = true; + this._fileShareSubscriptionDropdown.loading = true; + this._blobContainerSubscriptionDropdown.loading = true; + try { + const subscriptionDropdownValues = await this.migrationStateModel.getSubscriptionsDropdownValues(); + this._fileShareSubscriptionDropdown.values = subscriptionDropdownValues; + this._networkShareContainerSubscriptionDropdown.values = subscriptionDropdownValues; + this._blobContainerSubscriptionDropdown.values = subscriptionDropdownValues; + } catch (error) { + console.log(error); + } finally { + this._networkShareContainerSubscriptionDropdown.loading = false; + this._fileShareSubscriptionDropdown.loading = false; + this._blobContainerSubscriptionDropdown.loading = false; + } } } private async loadNetworkShareStorageDropdown(): Promise { - this._networkShareContainerStorageAccountDropdown.loading = true; - try { - this._networkShareContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel.databaseBackup.subscription); - this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(0); - } finally { - this._networkShareContainerStorageAccountDropdown.loading = false; + if (!this.migrationStateModel._databaseBackup.storageAccount) { + this._networkShareContainerStorageAccountDropdown.loading = true; + try { + this._networkShareContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription); + } catch (error) { + console.log(error); + } finally { + this._networkShareContainerStorageAccountDropdown.loading = false; + } } } private async loadFileShareStorageDropdown(): Promise { - this._fileShareStorageAccountDropdown.loading = true; - this._fileShareFileShareDropdown.loading = true; - try { - this._fileShareStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel.databaseBackup.subscription); - this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(0); - } catch (error) { - this.migrationStateModel._fileShares = undefined!; - } finally { - await this.loadFileShareDropdown(); - this._fileShareStorageAccountDropdown.loading = false; - this._fileShareFileShareDropdown.loading = false; - + if (!this.migrationStateModel._databaseBackup.storageAccount) { + this._fileShareStorageAccountDropdown.loading = true; + this._fileShareFileShareDropdown.loading = true; + try { + this._fileShareStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription); + } catch (error) { + console.log(error); + } finally { + this._fileShareStorageAccountDropdown.loading = false; + this._fileShareFileShareDropdown.loading = false; + } } } private async loadblobStorageDropdown(): Promise { - this._blobContainerStorageAccountDropdown.loading = true; - this._blobContainerBlobDropdown.loading = true; - try { - this._blobContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel.databaseBackup.subscription); - this.migrationStateModel.databaseBackup.storageAccount = this.migrationStateModel.getStorageAccount(0); - } catch (error) { - this.migrationStateModel._blobContainers = undefined!; - } finally { - await this.loadBlobContainerDropdown(); - this._blobContainerStorageAccountDropdown.loading = false; + if (!this.migrationStateModel._databaseBackup.storageAccount) { + this._blobContainerStorageAccountDropdown.loading = true; this._blobContainerBlobDropdown.loading = true; + try { + this._blobContainerStorageAccountDropdown.values = await this.migrationStateModel.getStorageAccountValues(this.migrationStateModel._databaseBackup.subscription); + } catch (error) { + console.log(error); + } finally { + this._blobContainerStorageAccountDropdown.loading = false; + this._blobContainerBlobDropdown.loading = true; + } } } private async loadFileShareDropdown(): Promise { - this._fileShareFileShareDropdown.loading = true; - try { - this._fileShareFileShareDropdown.values = await this.migrationStateModel.getFileShareValues(this.migrationStateModel.databaseBackup.subscription, this.migrationStateModel.databaseBackup.storageAccount); - this.migrationStateModel.databaseBackup.fileShare = this.migrationStateModel.getFileShare(0); - } catch (error) { - console.log(error); - } finally { - this._fileShareFileShareDropdown.loading = false; + if (!this.migrationStateModel._fileShares) { + this._fileShareFileShareDropdown.loading = true; + try { + this._fileShareFileShareDropdown.values = await this.migrationStateModel.getFileShareValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.storageAccount); + } catch (error) { + console.log(error); + } finally { + this._fileShareFileShareDropdown.loading = false; + } } } private async loadBlobContainerDropdown(): Promise { - this._blobContainerBlobDropdown.loading = true; - try { - this._blobContainerBlobDropdown.values = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel.databaseBackup.subscription, this.migrationStateModel.databaseBackup.storageAccount); - this.migrationStateModel.databaseBackup.blobContainer = this.migrationStateModel.getBlobContainer(0); - } catch (error) { - console.log(error); - } finally { - this._blobContainerBlobDropdown.loading = false; + if (!this.migrationStateModel._blobContainers) { + this._blobContainerBlobDropdown.loading = true; + try { + this._blobContainerBlobDropdown.values = await this.migrationStateModel.getBlobContainerValues(this.migrationStateModel._databaseBackup.subscription, this.migrationStateModel._databaseBackup.storageAccount); + } catch (error) { + console.log(error); + } finally { + this._blobContainerBlobDropdown.loading = false; + } } } } diff --git a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts index ee5e718730..2e9285b230 100644 --- a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts +++ b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts @@ -4,19 +4,22 @@ *--------------------------------------------------------------------------------------------*/ 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 * as constants from '../models/strings'; -import * as os from 'os'; -import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; +import { createInformationRow, WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; +import { getMigrationController, getMigrationControllerAuthKeys, getMigrationControllerMonitoringData } from '../api/azure'; +import { IconPathHelper } from '../constants/iconPathHelper'; export class IntergrationRuntimePage extends MigrationWizardPage { private migrationControllerDropdown!: azdata.DropDownComponent; - private _connectionStatus!: azdata.InfoBoxComponent; private _view!: azdata.ModelView; private _form!: azdata.FormBuilder; + private _statusLoadingComponent!: azdata.LoadingComponent; + private _migrationDetailsContainer!: azdata.FlexContainer; constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) { super(wizard, azdata.window.createWizardPage(constants.IR_PAGE_TITLE), migrationStateModel); @@ -35,7 +38,10 @@ export class IntergrationRuntimePage extends MigrationWizardPage { dialog.initialize(); }); - this._connectionStatus = view.modelBuilder.infoBox().component(); + this._migrationDetailsContainer = view.modelBuilder.flexContainer().withLayout({ + flexFlow: 'column' + }).component(); + this._statusLoadingComponent = view.modelBuilder.loadingComponent().withItem(this._migrationDetailsContainer).component(); this._form = view.modelBuilder.formContainer() .withFormItems( @@ -47,7 +53,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage { component: createNewController }, { - component: this._connectionStatus + component: this._statusLoadingComponent } ] @@ -59,23 +65,30 @@ export class IntergrationRuntimePage extends MigrationWizardPage { this.populateMigrationController(); this.wizard.registerNavigationValidator((pageChangeInfo) => { if (pageChangeInfo.newPage < pageChangeInfo.lastPage) { + this.wizard.message = { + text: '' + }; return true; } - - const errors: string[] = []; - if (((this.migrationControllerDropdown.value).displayName === constants.CONTROLLER_NOT_FOUND)) { - errors.push(constants.CONTROLLER_NOT_SETUP_ERROR); - } - - this.wizard.message = { - text: errors.join(os.EOL), - level: azdata.window.MessageLevel.Error - }; - - if (errors.length > 0) { + const state = this.migrationStateModel._migrationController.properties.integrationRuntimeState; + if (!this.migrationStateModel._migrationController) { + this.wizard.message = { + level: azdata.window.MessageLevel.Error, + text: constants.INVALID_CONTROLLER_ERROR + }; return false; } - + if (state !== 'Online') { + this.wizard.message = { + level: azdata.window.MessageLevel.Error, + text: constants.CONTROLLER_OFFLINE_ERROR + }; + return false; + } else { + this.wizard.message = { + text: '' + }; + } return true; }); } @@ -113,6 +126,16 @@ export class IntergrationRuntimePage extends MigrationWizardPage { width: WIZARD_INPUT_COMPONENT_WIDTH }).component(); + this.migrationControllerDropdown.onValueChanged(async (value) => { + if (value.selected) { + this.wizard.message = { + text: '' + }; + this.migrationStateModel._migrationController = this.migrationStateModel.getMigrationController(value.index); + await this.loadControllerStatus(); + } + }); + const flexContainer = this._view.modelBuilder.flexContainer().withItems([ descriptionText, noteText, @@ -126,49 +149,278 @@ export class IntergrationRuntimePage extends MigrationWizardPage { public async populateMigrationController(controllerStatus?: string): Promise { this.migrationControllerDropdown.loading = true; - let migrationContollerValues: azdata.CategoryValue[] = []; - - // TODO: Replace with this code when APIs are deployed. - // try{ - // this.migrationControllerDropdown.values = await this.migrationStateModel.getMigrationControllerValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._targetManagedInstance); - // this.migrationStateModel.migrationController = this.migrationStateModel.getMigrationController(0); - // } catch (e) { - - // } finally { - // this.migrationControllerDropdown.loading = false; - // } - - if (this.migrationStateModel.migrationController) { - - this._connectionStatus.updateProperties({ - text: constants.CONTROLLER_READY(this.migrationStateModel.migrationController!.name, this.migrationStateModel._nodeNames.join(', ')), - style: 'success' - }); - this._form.addFormItem({ - component: this._connectionStatus - }); - migrationContollerValues = [ - { - displayName: this.migrationStateModel.migrationController.name, - name: '' - } - ]; + try { + this.migrationControllerDropdown.values = await this.migrationStateModel.getMigrationControllerValues(this.migrationStateModel._targetSubscription, this.migrationStateModel._targetManagedInstance); + if (this.migrationStateModel._migrationController) { + this.migrationControllerDropdown.value = { + name: this.migrationStateModel._migrationController.id, + displayName: this.migrationStateModel._migrationController.name + }; + } else { + this.migrationStateModel._migrationController = this.migrationStateModel.getMigrationController(0); + } + } catch (error) { + console.log(error); + } finally { + this.migrationControllerDropdown.loading = false; } - else { - migrationContollerValues = [ - { - displayName: constants.CONTROLLER_NOT_FOUND, - name: '' - } - ]; - this._form.removeFormItem({ - component: this._connectionStatus - }); - } - this.migrationControllerDropdown.values = migrationContollerValues; - this.migrationControllerDropdown.loading = false; + } + private async loadControllerStatus(): Promise { + this._statusLoadingComponent.loading = true; + + try { + this._migrationDetailsContainer.clearItems(); + + if (this.migrationStateModel._migrationController) { + const controller = await getMigrationController( + 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._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) => { + return node.nodeName; + }); + const migrationControllerAuthKeys = await getMigrationControllerAuthKeys( + this.migrationStateModel._azureAccount, + this.migrationStateModel._targetSubscription, + this.migrationStateModel._migrationController.properties.resourceGroup, + this.migrationStateModel._migrationController.properties.location, + this.migrationStateModel._migrationController!.name + ); + + const migrationControllerTitle = this._view.modelBuilder.text().withProps({ + value: constants.CONTROLLER_DETAILS_HEADER(controller.name), + CSSStyles: { + 'font-weight': 'bold' + } + }).component(); + + const connectionStatusLabel = this._view.modelBuilder.text().withProps({ + value: constants.CONTROLLER_CONNECTION_STATUS, + CSSStyles: { + 'font-weight': 'bold', + 'width': '150px' + } + }).component(); + + const refreshStatus = this._view.modelBuilder.button().withProps({ + label: constants.REFRESH, + secondary: true, + width: '50px' + }).component(); + + + + const connectionLabelContainer = this._view.modelBuilder.flexContainer().withLayout({ + flexFlow: 'row', + alignItems: 'center' + }).withItems( + [ + connectionStatusLabel, + refreshStatus + ], + { + CSSStyles: { 'margin-right': '5px' } + } + ).component(); + + const connectionStatus = this._view.modelBuilder.infoBox().component(); + const connectionStatusLoader = this._view.modelBuilder.loadingComponent().withItem(connectionStatus).withProps({ + loading: false + }).component(); + refreshStatus.onDidClick(async (e) => { + connectionStatusLoader.loading = true; + + const controller = await getMigrationController( + 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._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) => { + return node.nodeName; + }); + + const state = controller.properties.integrationRuntimeState; + if (state === 'Online') { + connectionStatus.updateProperties({ + text: constants.CONTROLLER_READY(this.migrationStateModel._migrationController!.name, this.migrationStateModel._nodeNames.join(', ')), + style: 'success' + }); + } else { + connectionStatus.updateProperties({ + text: constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name), + style: 'error' + }); + } + + connectionStatusLoader.loading = false; + }); + + if (controller) { + const state = controller.properties.integrationRuntimeState; + if (state === 'Online') { + connectionStatus.updateProperties({ + text: constants.CONTROLLER_READY(this.migrationStateModel._migrationController!.name, this.migrationStateModel._nodeNames.join(', ')), + style: 'success' + }); + } else { + connectionStatus.updateProperties({ + text: constants.CONTROLLER_NOT_READY(this.migrationStateModel._migrationController!.name), + style: 'error' + }); + } + } + + const authenticationKeysLabel = this._view.modelBuilder.text().withProps({ + value: constants.AUTHENTICATION_KEYS, + CSSStyles: { + 'font-weight': 'bold' + } + }).component(); + + const migrationControllerAuthKeyTable = this._view.modelBuilder.declarativeTable().withProps({ + columns: [ + { + displayName: constants.NAME, + valueType: azdata.DeclarativeDataType.string, + width: '50px', + isReadOnly: true, + rowCssStyles: { + 'text-align': 'center' + } + }, + { + displayName: constants.AUTH_KEY_COLUMN_HEADER, + valueType: azdata.DeclarativeDataType.string, + width: '500px', + isReadOnly: true, + rowCssStyles: { + overflow: 'scroll' + } + }, + { + displayName: '', + valueType: azdata.DeclarativeDataType.component, + width: '15px', + isReadOnly: true, + }, + { + displayName: '', + valueType: azdata.DeclarativeDataType.component, + width: '15px', + isReadOnly: true, + } + ], + CSSStyles: { + 'margin-top': '5px' + } + }).component(); + + + const copyKey1Button = this._view.modelBuilder.button().withProps({ + iconPath: IconPathHelper.copy + }).component(); + + copyKey1Button.onDidClick((e) => { + vscode.env.clipboard.writeText(migrationControllerAuthKeyTable.dataValues![0][1].value); + vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); + }); + + const copyKey2Button = this._view.modelBuilder.button().withProps({ + iconPath: IconPathHelper.copy + }).component(); + + copyKey2Button.onDidClick((e) => { + vscode.env.clipboard.writeText(migrationControllerAuthKeyTable.dataValues![1][1].value); + vscode.window.showInformationMessage(constants.CONTROLLER_KEY_COPIED_HELP); + }); + + const refreshKey1Button = this._view.modelBuilder.button().withProps({ + iconPath: IconPathHelper.refresh + }).component(); + + refreshKey1Button.onDidClick((e) => {//TODO: add refresh logic + }); + + const refreshKey2Button = this._view.modelBuilder.button().withProps({ + iconPath: IconPathHelper.refresh + }).component(); + + refreshKey2Button.onDidClick((e) => {//TODO: add refresh logic + }); + + + migrationControllerAuthKeyTable.updateProperties({ + dataValues: [ + [ + { + value: constants.CONTROLLER_KEY1_LABEL + }, + { + value: migrationControllerAuthKeys.authKey1 + }, + { + value: copyKey1Button + }, + { + value: refreshKey1Button + } + ], + [ + { + value: constants.CONTROLLER_KEY2_LABEL + }, + { + value: migrationControllerAuthKeys.authKey2 + }, + { + value: copyKey2Button + }, + { + value: refreshKey2Button + } + ] + ] + }); + + this._migrationDetailsContainer.addItems( + [ + migrationControllerTitle, + 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), + connectionLabelContainer, + connectionStatusLoader, + authenticationKeysLabel, + migrationControllerAuthKeyTable + ] + ); + } + } catch (error) { + console.log(error); + this._migrationDetailsContainer.clearItems(); + } finally { + this._statusLoadingComponent.loading = false; + } + } } diff --git a/extensions/sql-migration/src/wizard/summaryPage.ts b/extensions/sql-migration/src/wizard/summaryPage.ts index 47ef130831..0dd97980d6 100644 --- a/extensions/sql-migration/src/wizard/summaryPage.ts +++ b/extensions/sql-migration/src/wizard/summaryPage.ts @@ -7,6 +7,7 @@ import * as azdata from 'azdata'; import { MigrationWizardPage } from '../models/migrationWizardPage'; import { MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine'; import * as constants from '../models/strings'; +import { createHeadingTextComponent, createInformationRow } from './wizardController'; export class SummaryPage extends MigrationWizardPage { private _view!: azdata.ModelView; @@ -35,22 +36,18 @@ export class SummaryPage extends MigrationWizardPage { public async onPageEnter(): Promise { this._flexContainer.addItems( [ - this.createHeadingTextComponent(constants.AZURE_ACCOUNT_LINKED), - this.createHeadingTextComponent(this.migrationStateModel.azureAccount.displayInfo.displayName), - - - this.createHeadingTextComponent(constants.MIGRATION_TARGET), - this.createInformationRow(constants.TYPE, constants.SUMMARY_MI_TYPE), - this.createInformationRow(constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name), - this.createInformationRow(constants.SUMMARY_MI_TYPE, this.migrationStateModel._targetManagedInstance.name), - this.createInformationRow(constants.SUMMARY_DATABASE_COUNT_LABEL, '1'), - - this.createHeadingTextComponent(constants.DATABASE_BACKUP_PAGE_TITLE), + createHeadingTextComponent(this._view, constants.AZURE_ACCOUNT_LINKED), + createHeadingTextComponent(this._view, this.migrationStateModel._azureAccount.displayInfo.displayName), + createHeadingTextComponent(this._view, constants.MIGRATION_TARGET), + createInformationRow(this._view, constants.TYPE, constants.SUMMARY_MI_TYPE), + createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name), + createInformationRow(this._view, constants.SUMMARY_MI_TYPE, this.migrationStateModel._targetManagedInstance.name), + createInformationRow(this._view, constants.SUMMARY_DATABASE_COUNT_LABEL, '1'), + createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_PAGE_TITLE), this.createNetworkContainerRows(), - - this.createHeadingTextComponent(constants.IR_PAGE_TITLE), - this.createInformationRow(constants.IR_PAGE_TITLE, this.migrationStateModel.migrationController?.name!), - this.createInformationRow(constants.SUMMARY_IR_NODE, this.migrationStateModel._nodeNames.join(', ')), + createHeadingTextComponent(this._view, constants.IR_PAGE_TITLE), + createInformationRow(this._view, constants.IR_PAGE_TITLE, this.migrationStateModel._migrationController?.name!), + createInformationRow(this._view, constants.SUMMARY_IR_NODE, this.migrationStateModel._nodeNames.join(', ')), ] ); @@ -66,82 +63,39 @@ export class SummaryPage extends MigrationWizardPage { protected async handleStateChange(e: StateChangeEvent): Promise { } - private createInformationRow(label: string, value: string): azdata.FlexContainer { - return this._view.modelBuilder.flexContainer() - .withLayout( - { - flexFlow: 'row', - alignItems: 'center', - }) - .withItems( - [ - this.creaetLabelTextComponent(label), - this.createTextCompononent(value) - ], - { - CSSStyles: { 'margin-right': '5px' } - }) - .component(); - } - - private createHeadingTextComponent(value: string): azdata.TextComponent { - const component = this.createTextCompononent(value); - component.updateCssStyles({ - 'font-size': '13px', - 'font-weight': 'bold' - }); - return component; - } - - - private creaetLabelTextComponent(value: string): azdata.TextComponent { - const component = this.createTextCompononent(value); - component.updateCssStyles({ - 'color': '#595959', - 'width': '250px' - }); - return component; - } - - private createTextCompononent(value: string): azdata.TextComponent { - return this._view.modelBuilder.text().withProps({ - value: value - }).component(); - } - private createNetworkContainerRows(): azdata.FlexContainer { const flexContainer = this._view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); - switch (this.migrationStateModel.databaseBackup.networkContainerType) { + switch (this.migrationStateModel._databaseBackup.networkContainerType) { case NetworkContainerType.NETWORK_SHARE: flexContainer.addItems( [ - this.createInformationRow(constants.TYPE, constants.NETWORK_SHARE), - this.createInformationRow(constants.PATH, this.migrationStateModel.databaseBackup.networkShareLocation), - this.createInformationRow(constants.USER_ACCOUNT, this.migrationStateModel.databaseBackup.windowsUser), - this.createInformationRow(constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel.databaseBackup.subscription.name), - this.createInformationRow(constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel.databaseBackup.storageAccount.name), + createInformationRow(this._view, constants.TYPE, constants.NETWORK_SHARE), + createInformationRow(this._view, constants.PATH, this.migrationStateModel._databaseBackup.networkShareLocation), + createInformationRow(this._view, constants.USER_ACCOUNT, this.migrationStateModel._databaseBackup.windowsUser), + createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name), + createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.storageAccount.name), ] ); break; case NetworkContainerType.FILE_SHARE: flexContainer.addItems( [ - this.createInformationRow(constants.TYPE, constants.FILE_SHARE), - this.createInformationRow(constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel.databaseBackup.subscription.name), - this.createInformationRow(constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel.databaseBackup.storageAccount.name), - this.createInformationRow(constants.FILE_SHARE, this.migrationStateModel.databaseBackup.fileShare.name), + createInformationRow(this._view, constants.TYPE, constants.FILE_SHARE), + createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name), + createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.storageAccount.name), + createInformationRow(this._view, constants.FILE_SHARE, this.migrationStateModel._databaseBackup.fileShare.name), ] ); break; case NetworkContainerType.BLOB_CONTAINER: flexContainer.addItems( [ - this.createInformationRow(constants.TYPE, constants.BLOB_CONTAINER), - this.createInformationRow(constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel.databaseBackup.blobContainer.subscription.name), - this.createInformationRow(constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel.databaseBackup.storageAccount.name), - this.createInformationRow(constants.BLOB_CONTAINER, this.migrationStateModel.databaseBackup.blobContainer.name), + createInformationRow(this._view, constants.TYPE, constants.BLOB_CONTAINER), + createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.blobContainer.subscription.name), + createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE, this.migrationStateModel._databaseBackup.storageAccount.name), + createInformationRow(this._view, constants.BLOB_CONTAINER, this.migrationStateModel._databaseBackup.blobContainer.name), ] ); } diff --git a/extensions/sql-migration/src/wizard/tempTargetSelectionPage.ts b/extensions/sql-migration/src/wizard/tempTargetSelectionPage.ts index 3e895319bf..9cc9384855 100644 --- a/extensions/sql-migration/src/wizard/tempTargetSelectionPage.ts +++ b/extensions/sql-migration/src/wizard/tempTargetSelectionPage.ts @@ -32,7 +32,8 @@ export class TempTargetSelectionPage extends MigrationWizardPage { this._managedInstanceSubscriptionDropdown.onValueChanged((e) => { if (e.selected) { this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(e.index); - this.migrationStateModel._targetManagedInstances = undefined!; + this.migrationStateModel._targetManagedInstance = undefined!; + this.migrationStateModel._migrationController = undefined!; this.populateManagedInstanceDropdown(); } }); @@ -47,7 +48,7 @@ export class TempTargetSelectionPage extends MigrationWizardPage { }).component(); this._managedInstanceDropdown.onValueChanged((e) => { if (e.selected) { - this.migrationStateModel.migrationControllers = undefined!; + this.migrationStateModel._migrationControllers = undefined!; this.migrationStateModel._targetManagedInstance = this.migrationStateModel.getManagedInstance(e.index); } }); @@ -77,35 +78,34 @@ export class TempTargetSelectionPage extends MigrationWizardPage { this.populateSubscriptionDropdown(); } public async onPageLeave(): Promise { - console.log(this.migrationStateModel._targetSubscription); - console.log(this.migrationStateModel._targetManagedInstance); } protected async handleStateChange(e: StateChangeEvent): Promise { } private async populateSubscriptionDropdown(): Promise { - this._managedInstanceSubscriptionDropdown.loading = true; - this._managedInstanceDropdown.loading = true; - - try { - this._managedInstanceSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues(); - this.migrationStateModel._targetSubscription = this.migrationStateModel.getSubscription(0); - } catch (e) { - this.migrationStateModel._targetManagedInstances = undefined!; - } finally { - this.populateManagedInstanceDropdown(); - this._managedInstanceSubscriptionDropdown.loading = false; - this._managedInstanceDropdown.loading = false; + if (!this.migrationStateModel._targetSubscription) { + this._managedInstanceSubscriptionDropdown.loading = true; + this._managedInstanceDropdown.loading = true; + try { + this._managedInstanceSubscriptionDropdown.values = await this.migrationStateModel.getSubscriptionsDropdownValues(); + } catch (e) { + console.log(e); + } finally { + this._managedInstanceSubscriptionDropdown.loading = false; + } } } private async populateManagedInstanceDropdown(): Promise { - this._managedInstanceDropdown.loading = true; - try { - this._managedInstanceDropdown.values = await this.migrationStateModel.getManagedInstanceValues(this.migrationStateModel._targetSubscription); - this.migrationStateModel._targetManagedInstance = this.migrationStateModel.getManagedInstance(0); - } finally { - this._managedInstanceDropdown.loading = false; + if (!this.migrationStateModel._targetManagedInstance) { + this._managedInstanceDropdown.loading = true; + try { + this._managedInstanceDropdown.values = await this.migrationStateModel.getManagedInstanceValues(this.migrationStateModel._targetSubscription); + } catch (e) { + console.log(e); + } finally { + this._managedInstanceDropdown.loading = false; + } } } } diff --git a/extensions/sql-migration/src/wizard/wizardController.ts b/extensions/sql-migration/src/wizard/wizardController.ts index acaadc1abc..0b6a7462d3 100644 --- a/extensions/sql-migration/src/wizard/wizardController.ts +++ b/extensions/sql-migration/src/wizard/wizardController.ts @@ -6,7 +6,7 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import * as mssql from '../../../mssql'; import { MigrationStateModel } from '../models/stateMachine'; -import { SourceConfigurationPage } from './sourceConfigurationPage'; +// import { SourceConfigurationPage } from './sourceConfigurationPage'; import { WIZARD_TITLE } from '../models/strings'; import { MigrationWizardPage } from '../models/migrationWizardPage'; import { SKURecommendationPage } from './skuRecommendationPage'; @@ -36,7 +36,7 @@ export class WizardController { const wizard = azdata.window.createWizard(WIZARD_TITLE, 'wide'); wizard.generateScriptButton.enabled = false; wizard.generateScriptButton.hidden = true; - const sourceConfigurationPage = new SourceConfigurationPage(wizard, stateModel); + // const sourceConfigurationPage = new SourceConfigurationPage(wizard, stateModel); const skuRecommendationPage = new SKURecommendationPage(wizard, stateModel); // const subscriptionSelectionPage = new SubscriptionSelectionPage(wizard, stateModel); const azureAccountsPage = new AccountsSelectionPage(wizard, stateModel); @@ -49,7 +49,7 @@ export class WizardController { // subscriptionSelectionPage, azureAccountsPage, tempTargetSelectionPage, - sourceConfigurationPage, + // sourceConfigurationPage, skuRecommendationPage, databaseBackupPage, integrationRuntimePage, @@ -88,3 +88,45 @@ export class WizardController { }); } } + +export function createInformationRow(view: azdata.ModelView, label: string, value: string): azdata.FlexContainer { + return view.modelBuilder.flexContainer() + .withLayout( + { + flexFlow: 'row', + alignItems: 'center', + }) + .withItems( + [ + creaetLabelTextComponent(view, label), + createTextCompononent(view, value) + ], + { + CSSStyles: { 'margin-right': '5px' } + }) + .component(); +} + +export function createHeadingTextComponent(view: azdata.ModelView, value: string): azdata.TextComponent { + const component = createTextCompononent(view, value); + component.updateCssStyles({ + 'font-size': '13px', + 'font-weight': 'bold' + }); + return component; +} + + +export function creaetLabelTextComponent(view: azdata.ModelView, value: string): azdata.TextComponent { + const component = createTextCompononent(view, value); + component.updateCssStyles({ + 'width': '250px' + }); + return component; +} + +export function createTextCompononent(view: azdata.ModelView, value: string): azdata.TextComponent { + return view.modelBuilder.text().withProps({ + value: value + }).component(); +}