Adding windows auth support to sql-migration and misc bug fixes. (#14816)

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

* Renaming of controller to sqlMigrationService

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

* Adding refresh azure token validation.

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

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

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

* Fixing all strings in migration service page and dialog

* fixed a string error in create service dialog

* Adding source config page to migration to support windows auth
Some refactorings for sqlDatabaseTree (still WIP)

* refactoring assessments code 1
introducing new interface for server assessments

* Filtering out non windows sql vms
Retaining selections made by user on assessments dialog

* Fix compile errors on sqlDatabaseTree

* Exposing migration status errors in cutover dialog

* Updating extension verion

* Correcting typos
Fixing compilation erros
Removing en-us from url
Fixing function names
Make UI calls unblocking

* Unblocking dialog in case of failed assessments
Localizing string
removing blocking code from UI
Fixing comments

* Fixed broken assessment page logic
This commit is contained in:
Aasim Khan
2021-03-23 07:48:26 -07:00
committed by GitHub
parent 780ca84f9a
commit 339d908d1d
20 changed files with 652 additions and 630 deletions

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { MigrationStateModel } from '../../models/stateMachine';
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine';
import { SqlDatabaseTree } from './sqlDatabasesTree';
import { SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
@@ -14,7 +14,6 @@ export type Issues = {
recommendation: string,
moreInfo: string,
impactedObjects: SqlMigrationImpactedObjectInfo[],
rowNumber: number
};
export class AssessmentResultsDialog {
@@ -31,10 +30,9 @@ export class AssessmentResultsDialog {
private _tree: SqlDatabaseTree;
constructor(public ownerUri: string, public model: MigrationStateModel, public title: string, private skuRecommendationPage: SKURecommendationPage, migrationType: string) {
constructor(public ownerUri: string, public model: MigrationStateModel, public title: string, private _skuRecommendationPage: SKURecommendationPage, private _targetType: MigrationTargetType) {
this._model = model;
let assessmentData = this.parseData(this._model);
this._tree = new SqlDatabaseTree(this._model, assessmentData, migrationType);
this._tree = new SqlDatabaseTree(this._model, this._targetType);
}
private async initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
@@ -42,8 +40,7 @@ export class AssessmentResultsDialog {
dialog.registerContent(async (view) => {
try {
const resultComponent = await this._tree.createComponentResult(view);
const treeComponent = await this._tree.createComponent(view);
const treeComponent = await this._tree.createComponent(view, this._targetType === MigrationTargetType.SQLVM ? this.model._vmDbs : this._model._miDbs);
const flex = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'row',
height: '100%',
@@ -79,55 +76,22 @@ export class AssessmentResultsDialog {
const dialogSetupPromises: Thenable<void>[] = [];
dialogSetupPromises.push(this.initializeDialog(this.dialog));
azdata.window.openDialog(this.dialog);
await Promise.all(dialogSetupPromises);
await this._tree.initialize();
}
}
private parseData(model: MigrationStateModel): Map<string, Issues[]> {
// if there are multiple issues for the same DB, need to consolidate
// map DB name -> Assessment result items (issues)
// map assessment result items to description, recommendation, more info & impacted objects
let dbMap = new Map<string, Issues[]>();
model.assessmentResults?.forEach((element) => {
let issues: Issues;
issues = {
description: element.description,
recommendation: element.message,
moreInfo: element.helpLink,
impactedObjects: element.impactedObjects,
rowNumber: 0
};
if (element.targetName.includes(':')) {
let spliceIndex = element.targetName.indexOf(':');
let dbName = element.targetName.slice(spliceIndex + 1);
let dbIssues = dbMap.get(element.targetName);
if (dbIssues) {
dbMap.set(dbName, dbIssues.concat([issues]));
} else {
dbMap.set(dbName, [issues]);
}
} else {
let dbIssues = dbMap.get(element.targetName);
if (dbIssues) {
dbMap.set(element.targetName, dbIssues.concat([issues]));
} else {
dbMap.set(element.targetName, [issues]);
}
}
});
return dbMap;
}
protected async execute() {
this.model._migrationDbs = this._tree.selectedDbs();
this.skuRecommendationPage.refreshDatabaseCount(this._model._migrationDbs.length);
if (this._targetType === MigrationTargetType.SQLVM) {
this._model._vmDbs = this._tree.selectedDbs();
} else {
this._model._miDbs = this._tree.selectedDbs();
}
this._skuRecommendationPage.refreshCardText();
this.model.refreshDatabaseBackupPage = true;
this._isOpen = false;
}
@@ -136,7 +100,6 @@ export class AssessmentResultsDialog {
this._isOpen = false;
}
public get isOpen(): boolean {
return this._isOpen;
}

View File

@@ -1,11 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
export abstract class AssessmentDialogComponent {
abstract createComponent(view: azdata.ModelView): Promise<azdata.Component>;
}

View File

@@ -3,10 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { AssessmentDialogComponent } from './model/assessmentDialogComponent';
export class SqlAssessmentResult extends AssessmentDialogComponent {
export class SqlAssessmentResult {
async createComponent(view: azdata.ModelView): Promise<azdata.Component> {
const title = this.createTitleComponent(view);

View File

@@ -3,9 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { AssessmentDialogComponent } from './model/assessmentDialogComponent';
export class SqlAssessmentResultList extends AssessmentDialogComponent {
export class SqlAssessmentResultList {
async createComponent(view: azdata.ModelView): Promise<azdata.Component> {
return view.modelBuilder.divContainer().withItems([

View File

@@ -3,49 +3,54 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
import { MigrationStateModel } from '../../models/stateMachine';
import { Issues } from './assessmentResultsDialog';
import { AssessmentDialogComponent } from './model/assessmentDialogComponent';
import { SqlMigrationAssessmentResultItem, SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine';
type DbIssues = {
name: string,
issues: Issues[]
const styleLeft: azdata.CssStyles = {
'border': 'none',
'text-align': 'left',
'white-space': 'nowrap',
'text-overflow': 'ellipsis',
'overflow': 'hidden'
};
const styleRight: azdata.CssStyles = {
'border': 'none',
'text-align': 'right',
'white-space': 'nowrap',
'text-overflow': 'ellipsis',
'overflow': 'hidden'
};
export class SqlDatabaseTree extends AssessmentDialogComponent {
public static excludeDbs: Array<string> = ['master', 'tempdb', 'msdb', 'model'];
private _model!: MigrationStateModel;
private instanceTable!: azdata.ComponentBuilder<azdata.DeclarativeTableComponent, azdata.DeclarativeTableProperties>;
private databaseTable!: azdata.ComponentBuilder<azdata.DeclarativeTableComponent, azdata.DeclarativeTableProperties>;
private _assessmentResultsTable!: azdata.ComponentBuilder<azdata.DeclarativeTableComponent, azdata.DeclarativeTableProperties>;
private _impactedObjectsTable!: azdata.ComponentBuilder<azdata.DeclarativeTableComponent, azdata.DeclarativeTableProperties>;
private _assessmentData: Map<string, Issues[]>;
export class SqlDatabaseTree {
private _instanceTable!: azdata.DeclarativeTableComponent;
private _databaseTable!: azdata.DeclarativeTableComponent;
private _assessmentResultsTable!: azdata.DeclarativeTableComponent;
private _impactedObjectsTable!: azdata.DeclarativeTableComponent;
private _recommendation!: azdata.TextComponent;
private _dbName!: azdata.TextComponent;
private _recommendationText!: azdata.TextComponent;
private _descriptionText!: azdata.TextComponent;
private _issues!: Issues;
private _impactedObjects!: SqlMigrationImpactedObjectInfo[];
private _objectDetailsType!: azdata.TextComponent;
private _objectDetailsName!: azdata.TextComponent;
private _objectDetailsSample!: azdata.TextComponent;
private _moreInfo!: azdata.TextComponent;
private _assessmentType!: string;
private _moreInfo!: azdata.HyperlinkComponent;
private _assessmentTitle!: azdata.TextComponent;
constructor(model: MigrationStateModel, assessmentData: Map<string, Issues[]>, assessmentType: string) {
super();
this._assessmentData = assessmentData;
this._model = model;
this._assessmentType = assessmentType;
if (this._assessmentType === 'vm') {
this._assessmentData.clear();
}
private _activeIssues!: SqlMigrationAssessmentResultItem[];
private _selectedIssue!: SqlMigrationAssessmentResultItem;
private _selectedObject!: SqlMigrationImpactedObjectInfo;
constructor(
private _model: MigrationStateModel,
private _targetType: MigrationTargetType
) {
}
async createComponent(view: azdata.ModelView): Promise<azdata.Component> {
async createComponent(view: azdata.ModelView, dbs: string[]): Promise<azdata.Component> {
const component = view.modelBuilder.flexContainer().withLayout({
height: '100%',
flexFlow: 'column'
@@ -57,32 +62,15 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
component.addItem(this.createSearchComponent(view), { flex: '0 0 auto' });
component.addItem(this.createInstanceComponent(view), { flex: '0 0 auto' });
component.addItem(await this.createDatabaseComponent(view), { flex: '1 1 auto' });
component.addItem(this.createDatabaseComponent(view, dbs), { flex: '1 1 auto' });
return component;
}
private async createDatabaseComponent(view: azdata.ModelView): Promise<azdata.DivContainer> {
let mapRowIssue = new Map<number, DbIssues>();
const styleLeft: azdata.CssStyles = {
'border': 'none',
'text-align': 'left',
'white-space': 'nowrap',
'text-overflow': 'ellipsis',
'overflow': 'hidden'
};
const styleRight: azdata.CssStyles = {
'border': 'none',
'text-align': 'right',
'white-space': 'nowrap',
'text-overflow': 'ellipsis',
'overflow': 'hidden'
};
this.databaseTable = view.modelBuilder.declarativeTable().withProps(
private createDatabaseComponent(view: azdata.ModelView, dbs: string[]): azdata.DivContainer {
this._databaseTable = view.modelBuilder.declarativeTable().withProps(
{
selectEffect: true,
width: '350px',
width: 200,
CSSStyles: {
'table-layout': 'fixed'
},
@@ -90,7 +78,7 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
{
displayName: '',
valueType: azdata.DeclarativeDataType.boolean,
width: '10%',
width: 20,
isReadOnly: false,
showCheckAll: true,
headerCssStyles: styleLeft,
@@ -99,125 +87,36 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
{
displayName: 'Databases', // TODO localize
valueType: azdata.DeclarativeDataType.string,
width: '75%',
width: 100,
isReadOnly: true,
headerCssStyles: styleLeft
},
{
displayName: 'Issues', // Incidents
valueType: azdata.DeclarativeDataType.string,
width: '15%',
width: 30,
isReadOnly: true,
headerCssStyles: styleRight,
ariaLabel: 'Issue Count' // TODO localize
}
],
dataValues: [
]
}
);
let dbList = await azdata.connection.listDatabases(this._model.sourceConnectionId);
if (dbList.length > 0) {
let rowNumber = 0;
this._assessmentData.forEach((value, key) => {
this.databaseTable.component().dataValues?.push(
[
{
value: false,
style: styleLeft
},
{
value: key,
style: styleLeft
},
{
value: value.length,
style: styleRight
}
]
);
let dbIssues = {
name: key,
issues: value
};
mapRowIssue.set(rowNumber, dbIssues);
dbList = dbList.filter(obj => obj !== key);
rowNumber = rowNumber + 1;
});
dbList.filter(db => !SqlDatabaseTree.excludeDbs.includes(db)).forEach((value) => {
this.databaseTable.component().dataValues?.push(
[
{
value: true,
style: styleLeft
},
{
value: value,
style: styleLeft
},
{
value: 0,
style: styleRight
}
]
);
let impactedObjects: SqlMigrationImpactedObjectInfo[] = [];
let issue: Issues[] = [{
description: 'No Issues',
recommendation: 'No Issues',
moreInfo: 'No Issues',
impactedObjects: impactedObjects,
rowNumber: rowNumber
}];
let noIssues = {
name: value,
issues: issue
};
mapRowIssue.set(rowNumber, noIssues);
rowNumber = rowNumber + 1;
});
}
this.databaseTable.component().onRowSelected(({ row }) => {
const rowInfo = mapRowIssue.get(row);
if (rowInfo) {
this._assessmentResultsTable.component().dataValues = [];
this._dbName.value = rowInfo.name;
if (rowInfo.issues[0].description === 'No Issues') {
this._recommendation.value = `Warnings (0 issues found)`;
} else {
this._recommendation.value = `Warnings (${rowInfo.issues.length} issues found)`;
}
// Need some kind of refresh method for declarative tables
let dataValues: string[][] = [];
rowInfo.issues.forEach(async (issue) => {
dataValues.push([
issue.description
]);
});
this._assessmentResultsTable.component().updateProperties({
data: dataValues
});
}
).component();
this._databaseTable.onRowSelected(({ row }) => {
this._databaseTable.focus();
this._activeIssues = this._model._assessmentResults?.databaseAssessments[row].issues;
this._selectedIssue = this._model._assessmentResults?.databaseAssessments[row].issues[0];
this._dbName.value = <string>this._databaseTable.dataValues![row][1].value;
this.refreshResults();
});
const tableContainer = view.modelBuilder.divContainer().withItems([this.databaseTable.component()]).withProps({
const tableContainer = view.modelBuilder.divContainer().withItems([this._databaseTable]).withProps({
CSSStyles: {
'width': '200px',
'margin-left': '15px',
},
'margin-right': '5px',
'margin-bottom': '10px'
}
}).component();
return tableContainer;
}
@@ -225,41 +124,30 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
private createSearchComponent(view: azdata.ModelView): azdata.DivContainer {
let resourceSearchBox = view.modelBuilder.inputBox().withProperties({
placeHolder: 'Search',
ariaLabel: 'searchbar'
}).component();
const searchContainer = view.modelBuilder.divContainer().withItems([resourceSearchBox]).withProps({
CSSStyles: {
'width': '200px',
'margin-left': '15px',
'margin-right': '5px'
},
'margin-right': '5px',
'margin-bottom': '10px'
}
}).component();
return searchContainer;
}
private createInstanceComponent(view: azdata.ModelView): azdata.DivContainer {
const styleLeft: azdata.CssStyles = {
'border': 'none',
'text-align': 'left'
};
const styleRight: azdata.CssStyles = {
'border': 'none',
'text-align': 'right'
};
this.instanceTable = view.modelBuilder.declarativeTable().withProps(
this._instanceTable = view.modelBuilder.declarativeTable().withProps(
{
selectEffect: true,
width: '100%',
width: 200,
columns: [
{
displayName: 'Instance',
valueType: azdata.DeclarativeDataType.string,
width: 5,
width: 150,
isReadOnly: true,
headerCssStyles: styleLeft,
ariaLabel: 'Database Migration Check' // TODO localize
@@ -267,31 +155,30 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
{
displayName: 'Warnings', // TODO localize
valueType: azdata.DeclarativeDataType.string,
width: 1,
width: 50,
isReadOnly: true,
headerCssStyles: styleRight
}
],
dataValues: [
[
{
value: 'SQL Server 1',
style: styleLeft
},
{
value: 2,
style: styleRight
}
]
]
});
const instanceContainer = view.modelBuilder.divContainer().withItems([this.instanceTable.component()]).withProps({
}).component();
const instanceContainer = view.modelBuilder.divContainer().withItems([this._instanceTable]).withProps({
CSSStyles: {
'width': '200px',
'margin-left': '15px',
},
'margin-right': '5px',
'margin-bottom': '10px'
}
}).component();
this._instanceTable.onRowSelected((e) => {
this._activeIssues = this._model._assessmentResults?.issues;
this._selectedIssue = this._model._assessmentResults?.issues[0];
this._dbName.value = <string>this._instanceTable.dataValues![0][0].value;
this.refreshResults();
});
return instanceContainer;
}
@@ -434,18 +321,12 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
]
]
}
);
this._impactedObjectsTable.component().onRowSelected(({ row }) => {
if (this._dbName.value) {
this._impactedObjects = this._issues.impactedObjects;
}
this._objectDetailsType.value = `Type: ${this._impactedObjects[row].objectType!}`;
this._objectDetailsName.value = `Name: ${this._impactedObjects[row].name}`;
this._objectDetailsSample.value = this._impactedObjects[row].impactDetail;
).component();
this._impactedObjectsTable.onRowSelected(({ row }) => {
this._selectedObject = this._impactedObjects[row];
this.refreshImpactedObject();
});
@@ -488,7 +369,7 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
}
}).component();
const container = view.modelBuilder.flexContainer().withItems([impactedObjectsTitle, this._impactedObjectsTable.component(), objectDetailsTitle, this._objectDetailsType, this._objectDetailsName, this._objectDetailsSample]).withLayout({
const container = view.modelBuilder.flexContainer().withItems([impactedObjectsTitle, this._impactedObjectsTable, objectDetailsTitle, this._objectDetailsType, this._objectDetailsName, this._objectDetailsSample]).withLayout({
flexFlow: 'column'
}).component();
@@ -534,13 +415,7 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
'margin-block-end': '0px'
}
}).component();
this._moreInfo = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: '',
CSSStyles: {
'font-size': '12px',
'width': '250px'
}
}).component();
this._moreInfo = view.modelBuilder.hyperlink().component();
const container = view.modelBuilder.flexContainer().withItems([descriptionTitle, this._descriptionText, recommendationTitle, this._recommendationText, moreInfo, this._moreInfo]).withLayout({
@@ -555,7 +430,8 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
this._assessmentTitle = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: '',
CSSStyles: {
'font-size': '14px',
'font-size': '15px',
'line-size': '19px',
'padding-bottom': '15px',
'border-bottom': 'solid 1px'
}
@@ -568,7 +444,8 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
const title = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: 'Target Platform',
CSSStyles: {
'font-size': '14px',
'font-size': '13px',
'line-size': '19px',
'margin-block-start': '0px',
'margin-block-end': '2px'
}
@@ -580,7 +457,7 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
private createPlatformComponent(view: azdata.ModelView): azdata.TextComponent {
const impact = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
title: 'Platform', // TODO localize
value: 'Azure SQL Managed Instance',
value: (this._targetType === MigrationTargetType.SQLVM) ? 'Azure SQL Virtual Machine' : 'Azure SQL Managed Instance',
CSSStyles: {
'font-size': '18px',
'margin-block-start': '0px',
@@ -666,49 +543,162 @@ export class SqlDatabaseTree extends AssessmentDialogComponent {
]
]
}
);
).component();
this._assessmentResultsTable.component().onRowSelected(({ row }) => {
this._descriptionText.value = this._assessmentResultsTable.component().data![row][0];
if (this._dbName.value) {
this._issues = this._assessmentData.get(this._dbName.value)![row];
this._moreInfo.value = this._issues.moreInfo;
this._impactedObjects = this._issues.impactedObjects;
let data: { value: string; }[][] = [];
this._impactedObjects.forEach(async (impactedObject) => {
data.push([
{
value: impactedObject.objectType
},
{
value: impactedObject.name
}
]);
});
this._assessmentTitle.value = this._issues.description;
this._impactedObjectsTable.component().updateProperties({
dataValues: data
});
}
this._assessmentResultsTable.onRowSelected(({ row }) => {
this._selectedIssue = this._activeIssues[row];
this.refreshAssessmentDetails();
});
return this._assessmentResultsTable.component();
return this._assessmentResultsTable;
}
public selectedDbs(): string[] {
let result: string[] = [];
this.databaseTable.component().dataValues?.forEach((arr) => {
this._databaseTable.dataValues?.forEach((arr) => {
if (arr[0].value === true) {
result.push(arr[1].value.toString());
}
});
return result;
}
public refreshResults(): void {
const assessmentResults: azdata.DeclarativeTableCellValue[][] = [];
this._activeIssues.forEach((v) => {
assessmentResults.push(
[
{
value: v.checkId
}
]
);
});
this._assessmentResultsTable.dataValues = assessmentResults;
this._selectedIssue = this._activeIssues[0];
this.refreshAssessmentDetails();
}
public refreshAssessmentDetails(): void {
if (this._selectedIssue) {
this._assessmentTitle.value = this._selectedIssue.checkId;
this._descriptionText.value = this._selectedIssue.description;
this._moreInfo.url = this._selectedIssue.helpLink;
this._moreInfo.label = this._selectedIssue.helpLink;
this._impactedObjects = this._selectedIssue.impactedObjects;
this._recommendationText.value = this._selectedIssue.message; // Expose correct property for recommendation.
this._impactedObjectsTable.dataValues = this._selectedIssue.impactedObjects.map((object) => {
return [
{
value: object.objectType
},
{
value: object.name
}
];
});
this._selectedObject = this._selectedIssue.impactedObjects[0];
}
else {
this._assessmentTitle.value = '';
this._descriptionText.value = '';
this._moreInfo.url = '';
this._moreInfo.label = '';
this._recommendationText.value = '';
this._impactedObjectsTable.dataValues = [];
}
this.refreshImpactedObject();
}
public refreshImpactedObject(): void {
if (this._selectedObject) {
this._objectDetailsType.value = `Type: ${this._selectedObject.objectType!}`;
this._objectDetailsName.value = `Name: ${this._selectedObject.name}`;
this._objectDetailsSample.value = this._selectedObject.impactDetail;
} else {
this._objectDetailsType.value = ``;
this._objectDetailsName.value = ``;
this._objectDetailsSample.value = '';
}
}
public async initialize(): Promise<void> {
let instanceTableValues: azdata.DeclarativeTableCellValue[][] = [];
let databaseTableValues: azdata.DeclarativeTableCellValue[][] = [];
const excludedDatabases = ['master', 'msdb', 'tempdb', 'model'];
const dbList = (await azdata.connection.listDatabases(this._model.sourceConnectionId)).filter(db => !excludedDatabases.includes(db));
const selectedDbs = (this._targetType === MigrationTargetType.SQLVM) ? this._model._vmDbs : this._model._miDbs;
const serverName = (await this._model.getSourceConnectionProfile()).serverName;
if (this._targetType === MigrationTargetType.SQLVM || !this._model._assessmentResults) {
instanceTableValues = [
[
{
value: serverName,
style: styleLeft
},
{
value: '0',
style: styleRight
}
]
];
dbList.forEach((db) => {
databaseTableValues.push(
[
{
value: selectedDbs.includes(db),
style: styleLeft
},
{
value: db,
style: styleLeft
},
{
value: '0',
style: styleRight
}
]
);
});
} else {
instanceTableValues = [
[
{
value: serverName,
style: styleLeft
},
{
value: this._model._assessmentResults.issues.length,
style: styleRight
}
]
];
this._model._assessmentResults.databaseAssessments.forEach((db) => {
databaseTableValues.push(
[
{
value: selectedDbs.includes(db.name),
style: styleLeft
},
{
value: db.name,
style: styleLeft
},
{
value: db.issues.length,
style: styleRight
}
]
);
});
}
this._dbName.value = serverName;
this._activeIssues = this._model._assessmentResults.issues;
this._selectedIssue = this._model._assessmentResults?.issues[0];
this.refreshResults();
this._instanceTable.dataValues = instanceTableValues;
this._databaseTable.dataValues = databaseTableValues;
}
}

View File

@@ -9,6 +9,7 @@ import { MigrationContext } from '../../models/migrationLocalStorage';
import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
import * as loc from '../../constants/strings';
import { getSqlServerName } from '../../api/utils';
import { EOL } from 'os';
export class MigrationCutoverDialog {
private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView;
@@ -353,6 +354,13 @@ export class MigrationCutoverDialog {
this._cutoverButton.enabled = false;
this._cancelButton.enabled = false;
await this._model.fetchStatus();
const errors = [];
errors.push(this._model.migrationStatus.properties.migrationFailureError?.message);
errors.push(this._model.migrationStatus.properties.migrationStatusDetails?.fileUploadBlockingErrors ?? []);
errors.push(this._model.migrationStatus.properties.migrationStatusDetails?.restoreBlockingReason);
this._dialogObject.message = {
text: errors.filter(e => e !== undefined).join(EOL)
};
const sqlServerInfo = await azdata.connection.getServerInfo(this._model._migration.sourceConnectionProfile.connectionId);
const sqlServerName = this._model._migration.sourceConnectionProfile.serverName;
const versionName = getSqlServerName(sqlServerInfo.serverMajorVersion!);

View File

@@ -20,6 +20,8 @@ export class MigrationCutoverDialogModel {
this._migration.subscription,
this._migration.migrationContext
));
// Logging status to help debugging.
console.log(this.migrationStatus);
}
public async startCutover(): Promise<DatabaseMigration | undefined> {