Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display Arc resource and assessment links #26214

Open
wants to merge 7 commits into
base: akshikagupta/dev/arc-resource-creation
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions extensions/sql-migration/src/api/azure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,10 +609,7 @@ export async function registerArcResourceProvider(
throw new Error(message);
}

return {
status: response.response!.status,
arcSqlServer: response.response!.data,
}
return response.response!.status;
}

export async function createOrUpdateMigrationArcSqlServerInstance(
Expand Down
2 changes: 2 additions & 0 deletions extensions/sql-migration/src/constants/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,5 @@ export function getSchemaMigrationStatusString(migration: DatabaseMigration | un
const schemaMigrationStatus = getSchemaMigrationStatus(migration) ?? DefaultSettingValue;
return loc.SchemaMigrationStatusLookup[schemaMigrationStatus] ?? schemaMigrationStatus;
}

export const forbiddenStatusCode = 403;
10 changes: 9 additions & 1 deletion extensions/sql-migration/src/constants/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const DATABASE_FOR_ASSESSMENT_PAGE_TITLE = localize('sql.migration.databa
export const DATABASE_FOR_ASSESSMENT_DESCRIPTION = localize('sql.migration.database.assessment.description', "Select the databases that you want to assess for migration to Azure SQL.");
export const SOURCE_INFRASTRUCTURE_TYPE = localize('sql.migration.source.infrastructure.type', "Source Infrastructure Type");
export const SOURCE_INFRASTRUCTURE_TYPE_INFO = localize('sql.migration.sourceinfrastructuretype.info', "Select Source Infrastructure type from the list of options");
export const IS_SQL_SERVER_ENABLED_BY_AZURE_ARC = localize('sql.migration.is.sql.server.enabled.by.azure.arc', "Is your source a 'SQL Server enabled by Azure Arc'?");
export const IS_SQL_SERVER_ENABLED_BY_AZURE_ARC = localize('sql.migration.is.sql.server.enabled.by.azure.arc', "Is your source SQL Server instance enabled by Azure Arc?");
export const SQL_SERVER_ENABLED_BY_AZURE_ARC_DETAILS = localize('sql.migration.sql.server.enabled.by.azure.arc.details', "SQL Server enabled by Azure Arc details");
export const SQL_SERVER_ENABLED_BY_AZURE_ARC = localize('sql.migration.sql.server.enabled.by.azure.arc', "SQL Server enabled by Azure Arc");
export const SELECT_A_SQL_SERVER_INSTANCE = localize('sql.migration.select.a.sql.server.instance', "Select a SQL Server instance");
Expand All @@ -114,6 +114,7 @@ export const ARC_RESOURCE_INFO = localize('sql.migration.arc.resource', "Select
export const NON_ARC_RESOURCE_SUBSCRIPTION_INFO = localize('sql.migration.non.arc.subscription', "Select the Azure subscription for creating Arc SQL Server that will be used for tracking the migration.");
export const NON_ARC_RESOURCE_LOCATION_INFO = localize('sql.migration.non.arc.location', "Select the Azure region for creating Arc SQL Server that will be used for tracking the migration.");
export const NON_ARC_RESOURCE_RESOURCE_GROUP_INFO = localize('sql.migration.non.arc.resource_group', "Select the resource group for creating Arc SQL Server that will be used for tracking the migration.");
export const ARC_RESOURCE_CREATION_INFO = localize('sql.migration.arc.resource.creation.info', "To help track the migration process in the Azure portal, a SQL Server instance resource will be created. There is no cost associated with this resource. Please choose a location, subscription and resource group in which to create the resource.");

// XEvents assessment
export const XEVENTS_ASSESSMENT_TITLE = localize('sql.migration.database.assessment.xevents.title', "Assess Ad-hoc or dynamic SQL");
Expand Down Expand Up @@ -252,11 +253,18 @@ export function CAN_BE_MIGRATED(eligibleDbs: number, totalDbs: number): string {
return localize('sql.migration.can.be.migrated', "{0}/{1} databases can be migrated without issues", eligibleDbs, totalDbs);
}

export const ARC_RESOURCE_CREATED_BEFORE_TEXT = localize('sql.migration.arc.resource.created.before.text', "To help track the migration process in the Azure portal, a SQL Server instance resource named ");
export const ARC_RESOURCE_CREATED_AFTER_TEXT = localize('sql.migration.arc.resource.created.after.text', " is created.");
export const ARC_RESOURCE_ASSESSMENT_COMPUTED_BEFORE_TEXT = localize('sql.migration.arc.resource.assessment.computed.before.text', "Assessment and SKU recommendation for this SQL Server instance enabled by Azure Arc has been computed. ");
export const ARC_RESOURCE_ASSESSMENT_COMPUTED_HYPERLINK_TEXT = localize('sql.migration.arc.resource.assessment.computed.after.text', "Click here to continue migration process from Azure Portal. ");
export const ARC_RESOURCE_ASSESSMENT_NOT_COMPUTED_TEXT = localize('sql.migration.arc.resource.assessment.not.computed.text', "Assessment and SKU recommendation for this SQL Server instance enabled by Azure Arc has not been computed.");

export const ASSESSMENT_MIGRATION_WARNING = localize('sql.migration.assessment.migration.warning', "Databases that are not ready for migration to Azure SQL Managed Instance or Azure SQL Database can be migrated to SQL Server on Azure Virtual Machines.");
export const ASSESSMENT_MIGRATION_WARNING_SQLDB = localize('sql.migration.assessment.migration.warning.sqldb', "Databases that are not ready for migration to Azure SQL Database can be migrated to SQL Server on Azure Virtual Machines. Alternatively, review assessment results for Azure SQL Managed Instance migration readiness.");
export const ASSESSMENT_MIGRATION_WARNING_SQLMI = localize('sql.migration.assessment.migration.warning.sqlmi', "Databases that are not ready for migration to Azure SQL Managed Instance can be migrated to SQL Server on Azure Virtual Machines. Alternatively, review assessment results for Azure SQL Database migration readiness.");
export const DATABASES_TABLE_TILE = localize('sql.migration.databases.table.title', "Databases");
export const SQL_SERVER_INSTANCE = localize('sql.migration.sql.server.instance', "SQL Server instance");
export const SOURCE_SQL_SERVER_INSTANCE = localize('sql.migration.sql.server.instance', "Source SQL Server instance");
export const LOAD_ASSESSMENT_REPORT = localize('sql.migration.load.assessment.report', "Load assessment report");
export const SAVE_ASSESSMENT_REPORT = localize('sql.migration.save.assessment.report', "Save assessment report");
export const SAVE_RECOMMENDATION_REPORT = localize('sql.migration.save.recommendation.report', "Save recommendation report");
Expand Down
9 changes: 7 additions & 2 deletions extensions/sql-migration/src/models/stateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { excludeDatabases, getEncryptConnectionValue, getSourceConnectionId, get
import { LoginMigrationModel } from './loginMigrationModel';
import { TdeMigrationDbResult, TdeMigrationModel, TdeValidationResult } from './tdeModels';
import { NetworkInterfaceModel } from '../api/dataModels/azure/networkInterfaceModel';
import { forbiddenStatusCode } from '../constants/helper';
const localize = nls.loadMessageBundle();

export enum ValidateIrState {
Expand Down Expand Up @@ -209,6 +210,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _arcResourceResourceGroup!: azurecore.azureResource.AzureResourceResourceGroup;
public _sourceArcSqlServers!: ArcSqlServer[];
public _arcSqlServer!: ArcSqlServer;
public _arcRpRegistrationStatus!: number;

public _subscriptions!: azurecore.azureResource.AzureResourceSubscription[];
public _targetSubscription!: azurecore.azureResource.AzureResourceSubscription;
Expand Down Expand Up @@ -1142,14 +1144,17 @@ export class MigrationStateModel implements Model, vscode.Disposable {

public async registerArcResourceProvider() {
try {
return await registerArcResourceProvider(
const responseStatus = await registerArcResourceProvider(
this._arcResourceAzureAccount,
this._arcResourceSubscription,
);
this._arcRpRegistrationStatus = responseStatus;
} catch (error) {
if (error.message && error.message.includes("403")) {
this._arcRpRegistrationStatus = forbiddenStatusCode;
}
logError(TelemetryViews.DatabaseBackupPage, 'ErrorRegisteringArcResourceProvider', error);
}
return;
}

public async createOrUpdateArcSqlServerInstance(fullInstanceName: string) {
Expand Down
2 changes: 2 additions & 0 deletions extensions/sql-migration/src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import AdsTelemetryReporter, { TelemetryEventMeasures, TelemetryEventProperties } from '@microsoft/ads-extension-telemetry';
import { MigrationStateModel } from './models/stateMachine';
import * as constants from './constants/strings';
const packageJson = require('../package.json');
let packageInfo = {
name: packageJson.name,
Expand Down Expand Up @@ -122,6 +123,7 @@ export function getTelemetryProps(migrationStateModel: MigrationStateModel): Tel
'subscriptionId': migrationStateModel._targetSubscription?.id,
'resourceGroup': migrationStateModel._resourceGroup?.name,
'targetType': migrationStateModel._targetType,
'sourceInfrastructureType': constants.SourceInfrastructureTypeLookup[migrationStateModel._sourceInfrastructureType],
'tenantId': tenantId,
};
}
Expand Down
11 changes: 3 additions & 8 deletions extensions/sql-migration/src/wizard/databaseSelectorPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,10 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
}
return false;
} else {
const registerArcProviderResponse = await this.migrationStateModel.registerArcResourceProvider();
if (registerArcProviderResponse?.status === 401) {
this.wizard.message = {
text: constants.REGISTER_ARC_RESOURCE_PROVIDER_UNAUTHORIZED_ERROR,
level: azdata.window.MessageLevel.Warning
}
return true;
await this.migrationStateModel.registerArcResourceProvider();
if (this.migrationStateModel._arcRpRegistrationStatus === 200) {
await this.migrationStateModel.createOrUpdateArcSqlServerInstance(fullInstanceName);
}
await this.migrationStateModel.createOrUpdateArcSqlServerInstance(fullInstanceName);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { AssessmentSummaryCard } from './assessmentSummaryCard';
import { MigrationTargetType } from '../../api/utils';
import { logError, TelemetryViews } from '../../telemetry';
import { getSourceConnectionProfile } from '../../api/sqlUtils';
import { IssueCategory } from '../../constants/helper';
import { forbiddenStatusCode, IssueCategory } from '../../constants/helper';
import { WizardController } from '../wizardController';

export class SKURecommendationPage extends MigrationWizardPage {
Expand All @@ -34,6 +34,12 @@ export class SKURecommendationPage extends MigrationWizardPage {
private _skipAssessmentSubText!: azdata.TextComponent;
private _targetSummaryComponent!: azdata.FlexContainer;
private _formContainer!: azdata.ComponentBuilder<azdata.FormContainer, azdata.ComponentProperties>;
private _arcServerLink!: azdata.HyperlinkComponent;
private _statusContainer!: azdata.FlexContainer;
private _arcResourceCreationComponent!: azdata.FlexContainer;
private _textBeforeArcServerLink!: azdata.TextComponent;
private _textAfterArcServerLink!: azdata.TextComponent;
private _arcResourceIcon!: azdata.ImageComponent;

private _assessmentSummaryCard!: azdata.FlexContainer;
private _assessmentSummaryCardLoader!: azdata.LoadingComponent;
Expand Down Expand Up @@ -66,6 +72,42 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._skuDataCollectionToolbar = new SkuDataCollectionToolbar(this, this.wizard, this.migrationStateModel);
const toolbar = this._skuDataCollectionToolbar.createToolbar(view);

this._arcResourceIcon = this._view.modelBuilder.image()
.withProps({
iconPath: IconPathHelper.completedMigration,
iconHeight: 16,
iconWidth: 16,
width: 16,
height: 16,
}).component();

this._textBeforeArcServerLink = this._view.modelBuilder.text().withProps({
value: constants.ARC_RESOURCE_CREATED_BEFORE_TEXT,
CSSStyles: { ...styles.BODY_CSS, 'margin-block-start': '0px', 'margin-block-end': '0px', 'margin-left': '8px', 'margin-right': '2px' }
}).component();

this._arcServerLink = this._view.modelBuilder.hyperlink()
.withProps({
label: '',
url: '',
CSSStyles: {
...styles.BODY_CSS,
}
}).component();

this._textAfterArcServerLink = this._view.modelBuilder.text().withProps({
value: constants.ARC_RESOURCE_CREATED_AFTER_TEXT,
CSSStyles: { ...styles.BODY_CSS, 'margin-block-start': '0px', 'margin-block-end': '0px', 'margin-left': '2px' }
}).component();

this._arcResourceCreationComponent = this._view.modelBuilder.flexContainer().withLayout({ flexWrap: 'wrap' }).withItems(
[
this._arcResourceIcon,
this._textBeforeArcServerLink,
this._arcServerLink,
this._textAfterArcServerLink
], { flex: '0 0 auto' }).component();

this._assessmentStatusIcon = this._view.modelBuilder.image()
.withProps({
iconPath: IconPathHelper.completedMigration,
Expand Down Expand Up @@ -110,9 +152,10 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._skipAssessmentCheckbox.onChanged(
async (value) => await this._setAssessmentState(false, true)));

const statusContainer = this._view.modelBuilder.flexContainer()
this._statusContainer = this._view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' })
.withItems([
this._arcResourceCreationComponent,
igContainer,
this._skuDataCollectionStatusContainer,
this._skipAssessmentCheckbox,
Expand All @@ -125,7 +168,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
this._formContainer = view.modelBuilder.formContainer()
.withFormItems([
{ component: toolbar },
{ component: statusContainer },
{ component: this._statusContainer },
{ component: this._targetSummaryComponent }
])
.withProps({
Expand Down Expand Up @@ -161,7 +204,6 @@ export class SKURecommendationPage extends MigrationWizardPage {
const component = view.modelBuilder.text()
.withProps({
CSSStyles: {

'font-size': '13px',
'font-weight': '400',
'line-height': '18px',
Expand Down Expand Up @@ -216,6 +258,45 @@ export class SKURecommendationPage extends MigrationWizardPage {
};

this._serverName = this.migrationStateModel.serverName || (await getSourceConnectionProfile()).serverName;
const arcSqlServer = this.migrationStateModel._arcSqlServer;
if (arcSqlServer) {
if (this.migrationStateModel._isSqlServerEnabledByArc) {
if (arcSqlServer.properties?.migration?.assessment?.assessmentUploadTime) {
this._textBeforeArcServerLink.value = constants.ARC_RESOURCE_ASSESSMENT_COMPUTED_BEFORE_TEXT;
await this._arcServerLink.updateProperties({
label: constants.ARC_RESOURCE_ASSESSMENT_COMPUTED_HYPERLINK_TEXT,
url: `https://portal.azure.com/#resource/${arcSqlServer.id}/migrationAssessment`,
});
this._arcResourceIcon.iconPath = IconPathHelper.completedMigration;
} else {
this._textBeforeArcServerLink.value = constants.ARC_RESOURCE_ASSESSMENT_NOT_COMPUTED_TEXT;
this._arcResourceIcon.iconPath = IconPathHelper.warning;
await this._arcServerLink.updateProperties({
label: '',
url: ''
});
}
this._textAfterArcServerLink.display = 'none';
} else {
this._textAfterArcServerLink.display = 'flex';
this._textBeforeArcServerLink.value = constants.ARC_RESOURCE_CREATED_BEFORE_TEXT;
this._textAfterArcServerLink.value = constants.ARC_RESOURCE_CREATED_AFTER_TEXT;
this._arcResourceIcon.iconPath = IconPathHelper.completedMigration;

await this._arcServerLink.updateProperties({
label: `${arcSqlServer.name}.`,
url: `https://portal.azure.com/#resource/${arcSqlServer.id}`,
});
}
} else {
if (this.migrationStateModel._arcRpRegistrationStatus === forbiddenStatusCode) {
this.wizard.message = {
text: constants.REGISTER_ARC_RESOURCE_PROVIDER_UNAUTHORIZED_ERROR,
level: azdata.window.MessageLevel.Warning
}
}
this._statusContainer.removeItem(this._arcResourceCreationComponent);
}

if (this.migrationStateModel._runAssessments) {
const errors: string[] = [];
Expand Down
21 changes: 20 additions & 1 deletion extensions/sql-migration/src/wizard/sourceSelectionSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export class SourceSelectionSection {
}).component();
const buttonGroup = 'isSqlServerEnabledByArc';
const sourceInfrastructureTypeContainer = this.createSourceInfrastructureTypeContainer();
const arcResourceCreationInfoContainer = this.createArcResourceCreationInfoContainer();

const isSqlServerEnabledByArcButton = this._view.modelBuilder.radioButton()
.withProps({
Expand Down Expand Up @@ -248,7 +249,7 @@ export class SourceSelectionSection {
arcResourceContainer.addItem(arcSqlServerDropdown);
}

const flex = view.modelBuilder.flexContainer().withItems([isSqlServerEnabledByArcButtonContainer, sourceInfrastructureTypeContainer, arcResourceContainer])
const flex = view.modelBuilder.flexContainer().withItems([isSqlServerEnabledByArcButtonContainer, sourceInfrastructureTypeContainer, arcResourceCreationInfoContainer, arcResourceContainer])
.withLayout({ flexFlow: 'column' })
.component();
this._disposables.push(
Expand All @@ -258,6 +259,7 @@ export class SourceSelectionSection {
this.migrationStateModel._isSqlServerEnabledByArc = checked;
selectSqlResourceHeading.value = constants.SQL_SERVER_ENABLED_BY_AZURE_ARC_DETAILS;
sourceInfrastructureTypeContainer.display = 'none';
arcResourceCreationInfoContainer.display = 'none';
await this._azureAccountsLabel.updateProperties({ description: constants.ARC_RESOURCE_ACCOUNT_INFO });
await this._azureSubscriptionLabel.updateProperties({ description: constants.ARC_RESOURCE_SUBSCRIPTION_INFO });
await this._azureLocationLabel.updateProperties({ description: constants.ARC_RESOURCE_LOCATION_INFO });
Expand All @@ -274,6 +276,7 @@ export class SourceSelectionSection {
this.migrationStateModel._isSqlServerEnabledByArc = !checked;
selectSqlResourceHeading.value = constants.SQL_SERVER_INSTANCE_DETAILS;
sourceInfrastructureTypeContainer.display = 'flex';
arcResourceCreationInfoContainer.display = 'flex';
await this._azureAccountsLabel.updateProperties({ description: '' });
await this._azureSubscriptionLabel.updateProperties({ description: constants.NON_ARC_RESOURCE_SUBSCRIPTION_INFO });
await this._azureLocationLabel.updateProperties({ description: constants.NON_ARC_RESOURCE_LOCATION_INFO });
Expand Down Expand Up @@ -333,6 +336,22 @@ export class SourceSelectionSection {
).withLayout({ flexFlow: 'row' }).component();
}

private createArcResourceCreationInfoContainer(): azdata.FlexContainer {
const arcResourceCreationInfo = this._view.modelBuilder.infoBox()
.withProps({
text: constants.ARC_RESOURCE_CREATION_INFO,
style: 'information',
width: WIZARD_INPUT_COMPONENT_WIDTH,
CSSStyles: { ...styles.BODY_CSS }
}).component();
return this._view.modelBuilder.flexContainer().withItems(
[
arcResourceCreationInfo,
],
{ flex: '0 0 auto' }
).withLayout({ flexFlow: 'row' }).component();
}

private createAzureAccountsDropdown(): azdata.FlexContainer {
this._azureAccountsLabel = this._view.modelBuilder.text()
.withProps({
Expand Down
26 changes: 21 additions & 5 deletions extensions/sql-migration/src/wizard/summaryPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,30 @@ export class SummaryPage extends MigrationWizardPage {
{ CSSStyles: { 'margin-right': '5px' } })
.component();

const arcServerHyperlink = this._view.modelBuilder.hyperlink()
.withProps({
url: '',
label: this.migrationStateModel._arcSqlServer?.name,
CSSStyles: { ...styles.BODY_CSS, 'margin': '0px', 'width': '300px', }
}).component();

const arcServerRow = this._view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'row', alignItems: 'center', })
.withItems([
createLabelTextComponent(
this._view,
constants.SQL_SERVER_INSTANCE,
{ ...styles.BODY_CSS, 'width': '300px' }
),
this.migrationStateModel._arcSqlServer ? arcServerHyperlink : this._view.modelBuilder.text().withProps({ value: '-' }).component(),
], { CSSStyles: { 'margin-right': '5px' } })
.component();

this._flexContainer
.addItems([
await createHeadingTextComponent(
this._view,
constants.SQL_SERVER_INSTANCE),
constants.SOURCE_SQL_SERVER_INSTANCE),
createInformationRow(
this._view,
constants.SOURCE_INFRASTRUCTURE_TYPE,
Expand All @@ -99,10 +118,7 @@ export class SummaryPage extends MigrationWizardPage {
constants.RESOURCE_GROUP,
this.migrationStateModel._arcResourceResourceGroup.name),

createInformationRow(
this._view,
constants.SQL_SERVER_INSTANCE,
this.migrationStateModel._arcSqlServer.name),
arcServerRow,

await createHeadingTextComponent(
this._view,
Expand Down