Fixing migration private preview bugs WIP 3 (#14928)

* Adding server name to wizard and dialog title
Surfacing async errors
Fixing a bunch of strings to match the mockup

* Adding auto refresh for migration status

* Removing errors for sql vm migration

* using new logic to get sql server username

* Fixing help links

* Removing unncessary await
This commit is contained in:
Aasim Khan
2021-03-31 10:19:59 -07:00
committed by GitHub
parent 00d2fadb7d
commit e762f19815
18 changed files with 198 additions and 106 deletions

View File

@@ -5,5 +5,5 @@
"migration-dashboard-title": "Azure SQL Migration",
"migration-dashboard-tasks": "Migration Tasks",
"migration-command-category": "Azure SQL Migration",
"start-migration-command": "Start Azure SQL Migration"
"start-migration-command": "Migrate to Azure SQL"
}

View File

@@ -187,7 +187,6 @@ export async function getSqlMigrationServiceMonitoringData(account: azdata.Accou
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
}
console.log(response);
return response.response.data;
}
@@ -198,7 +197,9 @@ export async function startDatabaseMigration(account: azdata.Account, subscripti
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
}
const asyncUrl = response.response.headers['azure-asyncoperation'];
return {
asyncUrl: asyncUrl,
status: response.response.status,
databaseMigration: response.response.data
};
@@ -227,6 +228,15 @@ export async function getMigrationStatus(account: azdata.Account, subscription:
return response.response.data;
}
export async function getMigrationAsyncOperationDetails(account: azdata.Account, subscription: Subscription, url: string): Promise<AzureAsyncOperationResource> {
const api = await getAzureCoreAPI();
const response = await api.makeAzureRestRequest(account, subscription, url.replace('https://management.azure.com/', ''), azurecore.HttpRequestMethod.GET, undefined, true);
if (response.errors.length > 0) {
throw new Error(response.errors.toString());
}
return response.response.data;
}
export async function listMigrationsBySqlMigrationService(account: azdata.Account, subscription: Subscription, sqlMigrationService: SqlMigrationService): Promise<DatabaseMigration[]> {
const api = await getAzureCoreAPI();
const path = `${sqlMigrationService.id}/listMigrations?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`;
@@ -359,6 +369,7 @@ export interface StartDatabaseMigrationRequest {
export interface StartDatabaseMigrationResponse {
status: number,
databaseMigration: DatabaseMigration
asyncUrl: string
}
export interface DatabaseMigration {
@@ -460,3 +471,12 @@ export interface DatabaseMigrationAzureBlob {
accountKey: string;
blobContainerName: string;
}
export interface AzureAsyncOperationResource {
name: string,
status: string,
startTime: string,
endTime: string,
percentComplete: number,
error: ErrorInfo
}

View File

@@ -9,7 +9,9 @@ const localize = nls.loadMessageBundle();
// #region wizard
export const WIZARD_TITLE = localize('sql-migration.wizard.title', "SQL Migration Wizard");
export function WIZARD_TITLE(instanceName: string): string {
return localize('sql-migration.wizard.title', "Migrate '{0}' to Azure SQL", instanceName);
}
export const SOURCE_CONFIGURATION_PAGE_TITLE = localize('sql.migration.wizard.source_configuration.title', "SQL Source Configuration");
// //#endregion
@@ -25,20 +27,20 @@ export const COLLECTING_SOURCE_CONFIGURATIONS_ERROR = (error: string = ''): stri
return localize('sql.migration.collecting_source_configurations.error', "There was an error when gathering information about your data configuration. {0}", error);
};
export const SKU_RECOMMENDATION_PAGE_TITLE = localize('sql.migration.wizard.sku.title', "Azure SQL Target Selection");
export const SKU_RECOMMENDATION_PAGE_TITLE = localize('sql.migration.wizard.sku.title', "Azure SQL Target");
export const SKU_RECOMMENDATION_ALL_SUCCESSFUL = (databaseCount: number): string => {
return localize('sql.migration.wizard.sku.all', "Based on the results of our source configuration scans, all {0} of your databases can be migrated to Azure SQL.", databaseCount);
return localize('sql.migration.wizard.sku.all', "Based on the assessment results, all {0} of your database(s) in online state can be migrated to Azure SQL.", databaseCount);
};
export const SKU_RECOMMENDATION_SOME_SUCCESSFUL = (migratableCount: number, databaseCount: number): string => {
return localize('sql.migration.wizard.sku.some', "Based on the results of our source configuration scans, {0} out of {1} of your databases can be migrated to Azure SQL.", migratableCount, databaseCount);
};
export const SKU_RECOMMENDATION_CHOOSE_A_TARGET = localize('sql.migration.wizard.sku.choose_a_target', "Choose a target Azure SQL");
export const SKU_RECOMMENDATION_CHOOSE_A_TARGET = localize('sql.migration.wizard.sku.choose_a_target', "Choose your Azure SQL target");
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 (PaaS)");
export const SKU_RECOMMENDATION_VM_CARD_TEXT = localize('sql.migration.sku.vm.card.title', "Azure SQL Virtual Machine (IaaS)");
export const SELECT_AZURE_MI = localize('sql.migration.select.azure.mi', "Select an Azure subscription and an Azure SQL Managed Instance for your target.");
export const SELECT_AZURE_VM = localize('sql.migration.select.azure.vm', "Select an Azure subscription and an Azure SQL Virtual Machine for your target.");
export const SKU_RECOMMENDATION_MI_CARD_TEXT = localize('sql.migration.sku.mi.card.title', "Azure SQL Managed Instance (PaaS)");
export const SKU_RECOMMENDATION_VM_CARD_TEXT = localize('sql.migration.sku.vm.card.title', "SQL Server on Azure Virtual Machine (IaaS)");
export const SELECT_AZURE_MI = localize('sql.migration.select.azure.mi', "Select your target Azure subscription and your target Azure SQL Managed Instance");
export const SELECT_AZURE_VM = localize('sql.migration.select.azure.vm', "Select your target Azure Subscription and your target SQL Server on Azure Virtual Machine for your target.");
export const SUBSCRIPTION_SELECTION_PAGE_TITLE = localize('sql.migration.wizard.subscription.title', "Azure Subscription Selection");
export const SUBSCRIPTION_SELECTION_AZURE_ACCOUNT_TITLE = localize('sql.migration.wizard.subscription.azure.account.title', "Azure Account");
export const SUBSCRIPTION_SELECTION_AZURE_SUBSCRIPTION_TITLE = localize('sql.migration.wizard.subscription.azure.subscription.title', "Azure Subscription");
@@ -47,7 +49,9 @@ export const SUBSCRIPTION_SELECTION_AZURE_PRODUCT_TITLE = localize('sql.migratio
export const ASSESSMENT_COMPLETED = (serverName: string): string => {
return localize('sql.migration.generic.congratulations', "We have completed the assessment of your SQL Server Instance '{0}'.", serverName);
};
export const ASSESSMENT_TILE = localize('sql.migration.assessment', "Assessment Dialog");
export function ASSESSMENT_TILE(serverName: string): string {
return localize('sql.migration.assessment', "Assessment Dialog for '{0}'", serverName);
}
// Accounts page
export const ACCOUNTS_SELECTION_PAGE_TITLE = localize('sql.migration.wizard.account.title', "Azure Account");
@@ -67,8 +71,8 @@ export const DATABASE_BACKUP_PAGE_TITLE = localize('sql.migration.database.page.
export const DATABASE_BACKUP_PAGE_DESCRIPTION = localize('sql.migration.database.page.description', "Select the location of your database backups to use for migration.");
export const DATABASE_BACKUP_NC_NETWORK_SHARE_RADIO_LABEL = localize('sql.migration.nc.network.share.radio.label', "My database backups are on a network share");
export const DATABASE_BACKUP_NC_BLOB_STORAGE_RADIO_LABEL = localize('sql.migration.nc.blob.storage.radio.label', "My database backups are in an Azure Storage Blob Container");
export const DATABASE_BACKUP_NC_FILE_SHARE_RADIO_LABEL = localize('sql.migration.nc.file.share.radio.label', "My database backups are in an Azure Storage File Share");
export const DATABASE_BACKUP_NC_BLOB_STORAGE_RADIO_LABEL = localize('sql.migration.nc.blob.storage.radio.label', "My database backups are in an Azure Storage Blob Container (Coming soon)");
export const DATABASE_BACKUP_NC_FILE_SHARE_RADIO_LABEL = localize('sql.migration.nc.file.share.radio.label', "My database backups are in an Azure Storage File Share (Coming soon)");
export const DATABASE_BACKUP_NETWORK_SHARE_HEADER_TEXT = localize('sql.migration.network.share.header.text', "Network share details");
export const DATABASE_BACKUP_NC_NETWORK_SHARE_HELP_TEXT = localize('sql.migration.network.share.help.text', "Provide the network share location that contains backups and the user credentials that has read access to the share");
@@ -97,8 +101,10 @@ export const DATABASE_BACKUP_FILE_SHARE_LABEL = localize('sql.migration.file.sha
export const DATABASE_BACKUP_FILE_SHARE_PLACEHOLDER = localize('sql.migration.file.share.placeholder', "Select share");
export const DATABASE_BACKUP_MIGRATION_MODE_LABEL = localize('sql.migration.database.migration.mode.label', "Migration mode");
export const DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION = localize('sql.migration.database.migration.mode.description', "Choose from the following migration modes to migrate to your Azure SQL target based on your downtime requirements.");
export const DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL = localize('sql.migration.database.migration.mode.online.label', "Online migration: Application downtime is limited to cut over at the end of migration.");
export const DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL = localize('sql.migration.database.migration.mode.offline.label', "Offline migration: Application downtime will start when the migration starts.");
export const DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL = localize('sql.migration.database.migration.mode.online.label', "Online migration");
export const DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION = localize('sql.migration.database.migration.mode.online.description', "Application downtime is limited to cut over at the end of migration.");
export const DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_DESCRIPTION = localize('sql.migration.database.migration.mode.offline.description', "Application downtime will start when the migration starts.");
export const DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL = localize('sql.migration.database.migration.mode.offline.label', "Offline migration");
export const DATABASE_BACKUP_EMAIL_NOTIFICATION_LABEL = localize('sql.migration.database.backup.email.notification.label', "Email notifications");
export const DATABASE_BACKUP_EMAIL_NOTIFICATION_CHECKBOX_LABEL = localize('sql.migration.database.backup.email.notification.checkbox.label', "Notify me when migration is complete");
export const NO_SUBSCRIPTIONS_FOUND = localize('sql.migration.no.subscription.found', "No subscription found");
@@ -125,15 +131,15 @@ export function TARGET_FILE_SHARE(dbName: string): string {
export function TARGET_BLOB_CONTAINER(dbName: string): string {
return localize('sql.migration.blob.container', "Select the container that contains the backup files for {0}", dbName);
}
export const ENTER_NETWORK_SHARE_INFORMATION = localize('sql.migration.enter.network.share.information', "Enter target names for selected databases");
export const ENTER_NETWORK_SHARE_INFORMATION = localize('sql.migration.enter.network.share.information', "Enter target names for selected source database(s)");
export const ENTER_BLOB_CONTAINER_INFORMATION = localize('sql.migration.blob.container.information', "Enter the target name and select the blob container location for selected databases");
export const ENTER_FILE_SHARE_INFORMATION = localize('sql.migration.enter.file.share.information', "Enter the target name and select the file share location of selected databases");
// integration runtime page
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "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_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.");
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 SELECT_A_SQL_MIGRATION_SERVICE = localize('sql.migration.select.a.migration.service', "Select Azure Database 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");
@@ -142,20 +148,20 @@ export const INVALID_SERVICE_ERROR = localize('sql.migration.invalid.migration.s
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 SQL_MIGRATION_SERVICE_DETAILS_HEADER(sqlMigrationServiceName: string) {
return localize('sql.migration.service.header', "Azure Data Migration Service \"{0}\" details:`", sqlMigrationServiceName);
return localize('sql.migration.service.header', "Azure Database Migration Service \"{0}\" details:`", sqlMigrationServiceName);
}
// 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 CREATE_MIGRATION_SERVICE_TITLE = localize('sql.migration.services.dialog.title', "Create Azure Database Migration Service");
export const MIGRATION_SERVICE_DIALOG_DESCRIPTION = localize('sql.migration.services.container.description', "Enter the information below to add a new Azure Database 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_CONTAINER_DESCRIPTION1 = localize('sql.migration.service.container.container.description1', "Azure Database Migration Service leverages Azure Data Factory's Self-hosted Integration Runtime to upload backups from on-premise network fie share to Azure.");
export const SERVICE_CONTAINER_DESCRIPTION2 = localize('sql.migration.service.container.container.description2', "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_STEP3 = localize('sql.migration.ir.setup.step3', "Step 3: Check connection between Azure Database Migration Service and Integration Runtime");
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");
@@ -164,10 +170,10 @@ 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 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);
return localize('sql.migration.service.not.ready', "Azure Database Migration Service is not registered. Azure Database Migration Service '{0}' needs to be registered with self-hosted Integration Runtime on any node.", serviceName);
}
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);
return localize('sql.migration.service.ready', "Azure Database 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.");
@@ -198,6 +204,8 @@ export const TYPE = localize('sql.migration.type', "Type");
export const PATH = localize('sql.migration.path', "Path");
export const USER_ACCOUNT = localize('sql.migration.path.user.account', "User Account");
export const VIEW_ALL = localize('sql.migration.view.all', "View All");
export const TARGET = localize('sql.migration.target', "Target");
export const AZURE_SQL = localize('sql.migration.azure.sql', "Azure SQL");
//Summary Page
export const SUMMARY_PAGE_TITLE = localize('sql.migration.summary.page.title', "Summary");
@@ -228,8 +236,8 @@ export const DASHBOARD_MIGRATE_TASK_BUTTON_DESCRIPTION = localize('sql.migration
export const DATABASE_MIGRATION_STATUS = localize('sql.migration.database.migration.status', "Database Migration Status");
export const HELP_VIDEO1_TITLE = localize('sql.migration.dashboard.video1.title', "Migrate SQL Server to SQL Managed Instance");
export const HELP_VIDEO2_TITLE = localize('sql.migration.dashboard.video2.title', "Migrate SQL Server to SQL Virtual Machine");
export const HELP_LINK1_TITLE = localize('sql.migration.dashboard.link1.title', "Migrating your SQL Server to cloud");
export const HELP_LINK1_DESCRIPTION = localize('sql.migration.dashboard.link1.description', "Lorem ipsum dolor sit amet, consectetur adipi. Lorem ipsum dolor sit amet, consectetur adipi. Lorem ipsum.");
export const HELP_LINK1_TITLE = localize('sql.migration.dashboard.link1.title', "Assessment rules for Azure SQL Managed Instance");
export const HELP_LINK1_DESCRIPTION = localize('sql.migration.dashboard.link1.description', "See the list of rules used to assess the feasibility of migrating your SQL Server to Azure SQL Managed Instance.");
export const HELP_TITLE = localize('sql.migration.dashboard.help.title', "Help Articles and Video Links");
export const PRE_REQ_TITLE = localize('sql.migration.pre.req.title', "Things you need before starting migration:");
export const PRE_REQ_1 = localize('sql.migration.pre.req.1', "Azure account details");
@@ -284,7 +292,7 @@ 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");
export const MIGRATION_MODE = localize('sql.migration.cutover.type', "Migration Mode");
export const START_TIME = localize('sql.migration.start.time', "Start Time");
export const FINISH_TIME = localize('sql.migration.finish.time', "Finish Time");

View File

@@ -41,6 +41,9 @@ export class DashboardWidget {
private _viewAllMigrationsButton!: azdata.ButtonComponent;
constructor() {
vscode.commands.registerCommand('sqlmigration.refreshMigrationTiles', () => {
this.refreshMigrations();
});
}
public register(): void {
@@ -220,7 +223,8 @@ export class DashboardWidget {
const inProgressMigrations = this._migrationStatus.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
return status === 'InProgress' || status === 'Creating' || status === 'Completing';
const provisioning = value.migrationContext.properties.provisioningState;
return status === 'InProgress' || status === 'Creating' || status === 'Completing' || provisioning === 'Creating';
});
this._inProgressMigrationButton.count.value = inProgressMigrations.length.toString();
@@ -261,17 +265,16 @@ export class DashboardWidget {
const cardTitleText = this._view.modelBuilder.text().withProps({ value: cardTitle }).withProps({
CSSStyles: {
'font-weight': 'bold',
'height': '20px',
'margin-top': '6px',
'height': '40px',
'margin-top': '15px',
'margin-bottom': '0px',
'width': '300px',
'font-size': '14px'
'font-size': '14px',
}
}).component();
const cardDescriptionText = this._view.modelBuilder.text().withProps({ value: cardDescription }).withProps({
CSSStyles: {
'height': '18px',
'height': '0px',
'margin-top': '0px',
'margin-bottom': '0px',
'width': '300px'
@@ -442,7 +445,7 @@ export class DashboardWidget {
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);
@@ -455,7 +458,7 @@ export class DashboardWidget {
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);
@@ -531,7 +534,7 @@ export class DashboardWidget {
const links = [{
title: loc.HELP_LINK1_TITLE,
description: loc.HELP_LINK1_DESCRIPTION,
link: 'https://www.microsoft.com' //TODO: add proper link over here.
link: 'https://docs.microsoft.com/azure/azure-sql/migration-guides/managed-instance/sql-server-to-sql-managed-instance-assessment-rules'
}];
const styles = {

View File

@@ -216,10 +216,21 @@ export class SqlDatabaseTree {
}).component();
this._instanceTable.onRowSelected((e) => {
this._instanceTable.focus();
this._activeIssues = this._model._assessmentResults?.issues;
this._selectedIssue = this._model._assessmentResults?.issues[0];
this._dbName.value = this._serverName;
this.refreshResults();
this._resultComponent.updateCssStyles({
'display': 'block'
});
this._dbMessageContainer.updateCssStyles({
'display': 'none'
});
if (this._model._targetType === MigrationTargetType.SQLMI) {
this.refreshResults();
}
});
return instanceContainer;
@@ -247,7 +258,6 @@ export class SqlDatabaseTree {
return container;
}
private createTopContainer(): azdata.FlexContainer {
const title = this.createTitleComponent();
const impact = this.createPlatformComponent();

View File

@@ -147,10 +147,6 @@ export class CreateSqlMigrationServiceDialog {
value: constants.MIGRATION_SERVICE_DIALOG_DESCRIPTION
}).component();
const formHeading = this._view.modelBuilder.text().withProps({
value: constants.CREATE_SERVICE_FORM_HEADING
}).component();
const subscriptionDropdownLabel = this._view.modelBuilder.text().withProps({
value: constants.SUBSCRIPTION
}).component();
@@ -184,9 +180,17 @@ export class CreateSqlMigrationServiceDialog {
value: await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.location)
}).component();
const targetlabel = this._view.modelBuilder.text().withProps({
value: constants.TARGET
}).component();
const targetText = this._view.modelBuilder.inputBox().withProps({
enabled: false,
value: constants.AZURE_SQL
}).component();
const flexContainer = this._view.modelBuilder.flexContainer().withItems([
dialogDescription,
formHeading,
subscriptionDropdownLabel,
this.migrationServiceSubscription,
locationDropdownLabel,
@@ -194,7 +198,9 @@ export class CreateSqlMigrationServiceDialog {
resourceGroupDropdownLabel,
this.migrationServiceResourceGroupDropdown,
migrationServiceNameLabel,
this.migrationServiceNameText
this.migrationServiceNameText,
targetlabel,
targetText
]).withLayout({
flexFlow: 'column'
}).component();
@@ -257,8 +263,12 @@ export class CreateSqlMigrationServiceDialog {
}
}).component();
const setupIRdescription = this._view.modelBuilder.text().withProps({
value: constants.SERVICE_CONTAINER_DESCRIPTION,
const setupIRdescription1 = this._view.modelBuilder.text().withProps({
value: constants.SERVICE_CONTAINER_DESCRIPTION1,
}).component();
const setupIRdescription2 = this._view.modelBuilder.text().withProps({
value: constants.SERVICE_CONTAINER_DESCRIPTION2,
}).component();
const irSetupStep1Text = this._view.modelBuilder.text().withProps({
@@ -353,7 +363,8 @@ export class CreateSqlMigrationServiceDialog {
this._setupContainer = this._view.modelBuilder.flexContainer().withItems(
[
setupIRHeadingText,
setupIRdescription,
setupIRdescription1,
setupIRdescription2,
irSetupStep1Text,
irSetupStep2Text,
this.migrationServiceAuthKeyTable,

View File

@@ -358,7 +358,15 @@ export class MigrationCutoverDialog {
this._copyDatabaseMigrationDetails.onDidClick(async (e) => {
await this.refreshStatus();
vscode.env.clipboard.writeText(JSON.stringify(this._model.migrationStatus, undefined, 2));
if (this._model.migrationOpStatus) {
vscode.env.clipboard.writeText(JSON.stringify({
'async-operation-details': this._model.migrationOpStatus,
'details': this._model.migrationStatus
}, undefined, 2));
} else {
vscode.env.clipboard.writeText(JSON.stringify(this._model.migrationStatus, undefined, 2));
}
vscode.window.showInformationMessage(loc.DETAILS_COPIED);
});
@@ -389,6 +397,7 @@ export class MigrationCutoverDialog {
this._cancelButton.enabled = false;
await this._model.fetchStatus();
const errors = [];
errors.push(this._model.migrationOpStatus.error.message);
errors.push(this._model.migrationStatus.properties.migrationFailureError?.message);
errors.push(this._model.migrationStatus.properties.migrationStatusDetails?.fileUploadBlockingErrors ?? []);
errors.push(this._model.migrationStatus.properties.migrationStatusDetails?.restoreBlockingReason);
@@ -409,7 +418,7 @@ export class MigrationCutoverDialog {
targetServerVersion = loc.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
}
const migrationStatusTextValue = this._model.migrationStatus.properties.migrationStatus;
const migrationStatusTextValue = this._model.migrationStatus.properties.migrationStatus ? this._model.migrationStatus.properties.migrationStatus : this._model.migrationStatus.properties.provisioningState;
let fullBackupFileName: string;
let lastAppliedSSN: string;

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getMigrationStatus, DatabaseMigration, startMigrationCutover, stopMigration } from '../../api/azure';
import { getMigrationStatus, DatabaseMigration, startMigrationCutover, stopMigration, getMigrationAsyncOperationDetails, AzureAsyncOperationResource } from '../../api/azure';
import { MigrationContext } from '../../models/migrationLocalStorage';
export enum MigrationStatus {
@@ -16,11 +16,19 @@ export enum MigrationStatus {
export class MigrationCutoverDialogModel {
public migrationStatus!: DatabaseMigration;
public migrationOpStatus!: AzureAsyncOperationResource;
constructor(public _migration: MigrationContext) {
}
public async fetchStatus(): Promise<void> {
if (this._migration.asyncUrl) {
this.migrationOpStatus = (await getMigrationAsyncOperationDetails(
this._migration.azureAccount,
this._migration.subscription,
this._migration.asyncUrl
));
}
this.migrationStatus = (await getMigrationStatus(
this._migration.azureAccount,
this._migration.subscription,

View File

@@ -143,7 +143,7 @@ export class MigrationStatusDialog {
});
migrationRow.push({
value: migration.migrationContext.properties.migrationStatus
value: migration.migrationContext.properties.migrationStatus ? migration.migrationContext.properties.migrationStatus : migration.migrationContext.properties.provisioningState
});
const targetMigrationIcon = this._view.modelBuilder.image().withProps({
@@ -247,7 +247,7 @@ export class MigrationStatusDialog {
}
},
{
displayName: loc.CUTOVER_TYPE,
displayName: loc.MIGRATION_MODE,
valueType: azdata.DeclarativeDataType.string,
width: '100px',
isReadOnly: true,

View File

@@ -31,7 +31,8 @@ export class MigrationStatusDialogModel {
} else if (category === 'Ongoing') {
filteredMigration = this._migrations.filter((value) => {
const status = value.migrationContext.properties.migrationStatus;
return status === 'InProgress' || status === 'Creating' || status === 'Completing';
const provisioning = value.migrationContext.properties.provisioningState;
return status === 'InProgress' || status === 'Creating' || status === 'Completing' || provisioning === 'Creating';
});
} else if (category === 'Succeeded') {
filteredMigration = this._migrations.filter((value) => {

View File

@@ -57,16 +57,19 @@ export class MigrationLocalStorage {
targetMI: SqlManagedInstance,
azureAccount: azdata.Account,
subscription: azureResource.AzureResourceSubscription,
controller: SqlMigrationService): void {
controller: SqlMigrationService,
asyncURL: string): void {
try {
const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
let migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
migrationMementos = migrationMementos.filter(m => m.migrationContext.id !== migrationContext.id);
migrationMementos.push({
sourceConnectionProfile: connectionProfile,
migrationContext: migrationContext,
targetManagedInstance: targetMI,
subscription: subscription,
azureAccount: azureAccount,
controller: controller
controller: controller,
asyncUrl: asyncURL
});
this.context.globalState.update(this.mementoToken, migrationMementos);
} catch (e) {
@@ -85,5 +88,6 @@ export interface MigrationContext {
targetManagedInstance: SqlManagedInstance,
azureAccount: azdata.Account,
subscription: azureResource.AzureResourceSubscription,
controller: SqlMigrationService
controller: SqlMigrationService,
asyncUrl: string
}

View File

@@ -399,6 +399,9 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getManagedInstanceValues(subscription: azureResource.AzureResourceSubscription, location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<azdata.CategoryValue[]> {
let managedInstanceValues: azdata.CategoryValue[] = [];
if (!this._azureAccount) {
return managedInstanceValues;
}
try {
this._targetManagedInstances = (await getAvailableManagedInstanceProducts(this._azureAccount, subscription)).filter((mi) => {
if (mi.location.toLowerCase() === location.name.toLowerCase() && mi.resourceGroup?.toLowerCase() === resourceGroup.name.toLowerCase()) {
@@ -488,6 +491,9 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getStorageAccountValues(subscription: azureResource.AzureResourceSubscription): Promise<azdata.CategoryValue[]> {
let storageAccountValues: azdata.CategoryValue[] = [];
if (!this._databaseBackup.resourceGroup) {
return storageAccountValues;
}
try {
const storageAccount = (await getAvailableStorageAccounts(this._azureAccount, subscription));
this._storageAccounts = storageAccount.filter(sa => {
@@ -652,7 +658,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
},
sourceLocation: {
fileShare: {
path: '',
path: this._databaseBackup.networkShareLocation,
username: this._databaseBackup.windowsUser,
password: this._databaseBackup.password,
}
@@ -680,14 +686,15 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._targetDatabaseNames[i],
requestBody
);
if (response.status === 201) {
if (response.status === 201 || response.status === 200) {
MigrationLocalStorage.saveMigration(
currentConnection!,
response.databaseMigration,
this._targetServerInstance,
this._azureAccount,
this._targetSubscription,
this._sqlMigrationService
this._sqlMigrationService,
response.asyncUrl
);
vscode.window.showInformationMessage(localize("sql.migration.starting.migration.message", 'Starting migration for database {0} to {1} - {2}', this._migrationDbs[i], this._targetServerInstance.name, this._targetDatabaseNames[i]));
}
@@ -695,6 +702,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
console.log(e);
vscode.window.showInformationMessage(e);
}
vscode.commands.executeCommand('sqlmigration.refreshMigrationTiles');
}
}
}

View File

@@ -139,7 +139,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
requiredIndicator: true,
}).component();
this._fileShareSubscription = view.modelBuilder.inputBox().withProps({
required: true,
required: false,
enabled: false
}).component();
@@ -150,7 +150,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}).component();
this._fileShareStorageAccountDropdown = view.modelBuilder.dropDown()
.withProps({
required: true
required: false
}).component();
this._fileShareStorageAccountDropdown.onValueChanged(async (value) => {
if (value.selected) {
@@ -195,7 +195,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}).component();
this._blobContainerSubscription = view.modelBuilder.inputBox()
.withProps({
required: true,
required: false,
enabled: false
}).component();
@@ -206,7 +206,7 @@ export class DatabaseBackupPage extends MigrationWizardPage {
}).component();
this._blobContainerStorageAccountDropdown = view.modelBuilder.dropDown()
.withProps({
required: true
required: false
}).component();
this._blobContainerStorageAccountDropdown.onValueChanged(async (value) => {
if (value.selected) {
@@ -335,7 +335,11 @@ export class DatabaseBackupPage extends MigrationWizardPage {
const azureAccountHeader = view.modelBuilder.text()
.withProps({
value: constants.DATABASE_BACKUP_NETWORK_SHARE_AZURE_ACCOUNT_HEADER,
width: WIZARD_INPUT_COMPONENT_WIDTH
width: WIZARD_INPUT_COMPONENT_WIDTH,
CSSStyles: {
'font-size': '14px',
'font-weight': 'bold'
}
}).component();
const azureAccountHelpText = view.modelBuilder.text()
@@ -429,7 +433,11 @@ export class DatabaseBackupPage extends MigrationWizardPage {
const networkShareDatabaseConfigHeader = view.modelBuilder.text().withProps({
value: constants.ENTER_NETWORK_SHARE_INFORMATION,
width: WIZARD_INPUT_COMPONENT_WIDTH
width: WIZARD_INPUT_COMPONENT_WIDTH,
CSSStyles: {
'font-size': '14px',
'font-weight': 'bold'
}
}).component();
this._networkShareDatabaseConfigContainer = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',

View File

@@ -104,17 +104,7 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
private migrationServiceDropdownContainer(): azdata.FlexContainer {
const descriptionText = this._view.modelBuilder.text().withProps({
value: constants.IR_PAGE_DESCRIPTION,
links: [
{
url: 'https://www.microsoft.com', // TODO: Add proper link
text: constants.LEARN_MORE
},
]
}).component();
const noteText = this._view.modelBuilder.text().withProps({
value: constants.IR_PAGE_NOTE
value: constants.IR_PAGE_DESCRIPTION
}).component();
const migrationServcieDropdownLabel = this._view.modelBuilder.text().withProps({
@@ -140,7 +130,6 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
const flexContainer = this._view.modelBuilder.flexContainer().withItems([
descriptionText,
noteText,
migrationServcieDropdownLabel,
this.migrationServiceDropdown
]).withLayout({

View File

@@ -44,9 +44,19 @@ export class MigrationModePage extends MigrationWizardPage {
const onlineButton = view.modelBuilder.radioButton().withProps({
label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
name: buttonGroup,
CSSStyles: {
'font-weight': 'bold'
},
checked: true
}).component();
const onlineDescription = view.modelBuilder.text().withProps({
value: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION,
CSSStyles: {
'margin': '0 0 10px 20px'
}
}).component();
this.migrationStateModel._databaseBackup.migrationCutover = MigrationCutover.ONLINE;
onlineButton.onDidChangeCheckedState((e) => {
@@ -57,9 +67,20 @@ export class MigrationModePage extends MigrationWizardPage {
const offlineButton = view.modelBuilder.radioButton().withProps({
label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
name: buttonGroup
name: buttonGroup,
CSSStyles: {
'font-weight': 'bold'
},
}).component();
const offlineDescription = view.modelBuilder.text().withProps({
value: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_DESCRIPTION,
CSSStyles: {
'margin': '0 0 10px 20px'
}
}).component();
offlineButton.onDidChangeCheckedState((e) => {
if (e) {
vscode.window.showInformationMessage('Feature coming soon');
@@ -71,7 +92,9 @@ export class MigrationModePage extends MigrationWizardPage {
const flexContainer = view.modelBuilder.flexContainer().withItems(
[
onlineButton,
offlineButton
onlineDescription,
offlineButton,
offlineDescription
]
).withLayout({
flexFlow: 'column'

View File

@@ -64,7 +64,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._view = view;
this._igComponent = this.createStatusComponent(view); // The first component giving basic information
this._detailsComponent = this.createDetailsComponent(view); // The details of what can be moved
this._chooseTargetComponent = this.createChooseTargetComponent(view);
this._chooseTargetComponent = await this.createChooseTargetComponent(view);
this._azureSubscriptionText = this.createAzureSubscriptionText(view);
@@ -223,7 +223,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
return component;
}
private createChooseTargetComponent(view: azdata.ModelView): azdata.DivContainer {
private async createChooseTargetComponent(view: azdata.ModelView): Promise<azdata.DivContainer> {
this._rbg = this._view!.modelBuilder.radioCardGroup().withProps({
cards: [],
@@ -279,8 +279,10 @@ export class SKURecommendationPage extends MigrationWizardPage {
descriptions
});
});
let miDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE, this, MigrationTargetType.SQLMI);
let vmDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE, this, MigrationTargetType.SQLVM);
const serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
let miDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLMI);
let vmDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLVM);
this._rbg.onLinkClick(async (value) => {
if (value.cardId === MigrationTargetType.SQLVM) {

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as os from 'os';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationSourceAuthenticationType, MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
@@ -47,24 +46,11 @@ export class SqlSourceConfigurationPage extends MigrationWizardPage {
private async createSourceCredentialContainer(): Promise<azdata.FormComponent> {
const connectionProfile = await this.migrationStateModel.getSourceConnectionProfile();
let username;
switch (connectionProfile.authenticationType) {
case 'SqlLogin':
username = connectionProfile.userName;
this.migrationStateModel._authenticationType = MigrationSourceAuthenticationType.Sql;
break;
case 'Integrated':
if (process.env.USERDOMAIN && process.env.USERNAME) {
username = process.env.USERDOMAIN + '\\' + process.env.USERNAME;
} else {
username = os.userInfo().username;
}
this.migrationStateModel._authenticationType = MigrationSourceAuthenticationType.Integrated;
break;
default:
username = '';
}
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>((await this.migrationStateModel.getSourceConnectionProfile()).providerId, azdata.DataProviderType.QueryProvider);
const query = 'select SUSER_NAME()';
const results = await queryProvider.runQueryAndReturn(await (azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId)), query);
const username = results.rows[0][0].displayValue;
this.migrationStateModel._authenticationType = connectionProfile.authenticationType === 'SqlLogin' ? MigrationSourceAuthenticationType.Sql : connectionProfile.authenticationType === 'Integrated' ? MigrationSourceAuthenticationType.Integrated : undefined!;
const sourceCredText = createHeadingTextComponent(this._view, constants.SOURCE_CREDENTIALS);

View File

@@ -33,7 +33,8 @@ export class WizardController {
}
private async createWizard(stateModel: MigrationStateModel): Promise<void> {
const wizard = azdata.window.createWizard(loc.WIZARD_TITLE, 'MigrationWizard', 'wide');
const serverName = (await stateModel.getSourceConnectionProfile()).serverName;
const wizard = azdata.window.createWizard(loc.WIZARD_TITLE(serverName), 'MigrationWizard', 'wide');
wizard.generateScriptButton.enabled = false;
wizard.generateScriptButton.hidden = true;
const skuRecommendationPage = new SKURecommendationPage(wizard, stateModel);