SQL Assessment Database Selector (#16030)

* wip

* wip

* database selector table

* fixed db icon

* wip

* fixed assessment results table

* replaced large query

* fix build error

* updated spacing and other fixes

* removed commented code

* added search bar, fix margins, disable checkbox for offline db

* change width of checkbox column

* changed api to require databases

* Revert "changed api to require databases"

This reverts commit 20fe2d8bd223bae90dfb09609225a1781267a01d.

* removed optional flag from databases parameter

* removed icons on assessment dialog page

* formatting changes, fixed search

* bump STS

* bumped extension version number

* one excludeDbs
This commit is contained in:
Christopher Suh
2021-07-21 20:58:32 -04:00
committed by GitHub
parent f390c4cbc2
commit 6d4608dd8b
11 changed files with 380 additions and 84 deletions

View File

@@ -1,6 +1,6 @@
{ {
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "3.0.0-release.115", "version": "3.0.0-release.116",
"downloadFileNames": { "downloadFileNames": {
"Windows_86": "win-x86-net5.0.zip", "Windows_86": "win-x86-net5.0.zip",
"Windows_64": "win-x64-net5.0.zip", "Windows_64": "win-x64-net5.0.zip",

View File

@@ -702,7 +702,7 @@ export namespace SchemaCompareCancellationRequest {
export interface SqlAssessmentParams { export interface SqlAssessmentParams {
ownerUri: string; ownerUri: string;
targetType: azdata.sqlAssessment.SqlAssessmentTargetType targetType: azdata.sqlAssessment.SqlAssessmentTargetType;
} }
export interface GenerateSqlAssessmentScriptParams { export interface GenerateSqlAssessmentScriptParams {
@@ -1016,10 +1016,11 @@ export namespace ProfilerSessionCreatedNotification {
/// ------------------------------- <Sql Migration> ----------------------------- /// ------------------------------- <Sql Migration> -----------------------------
export interface SqlMigrationAssessmentParams { export interface SqlMigrationAssessmentParams {
ownerUri: string; ownerUri: string;
databases: string[];
} }
export namespace GetSqlMigrationAssessmentItemsRequest { export namespace GetSqlMigrationAssessmentItemsRequest {
export const type = new RequestType<SqlAssessmentParams, mssql.AssessmentResult, void, void>('migration/getassessments'); export const type = new RequestType<SqlMigrationAssessmentParams, mssql.AssessmentResult, void, void>('migration/getassessments');
} }
// ------------------------------- <Sql Migration> ----------------------------- // ------------------------------- <Sql Migration> -----------------------------

View File

@@ -599,5 +599,5 @@ export interface AssessmentResult {
} }
export interface ISqlMigrationService { export interface ISqlMigrationService {
getAssessments(ownerUri: string): Promise<AssessmentResult | undefined>; getAssessments(ownerUri: string, databases: string[]): Promise<AssessmentResult | undefined>;
} }

View File

@@ -29,8 +29,8 @@ export class SqlMigrationService implements mssql.ISqlMigrationService {
context.registerService(constants.SqlMigrationService, this); context.registerService(constants.SqlMigrationService, this);
} }
async getAssessments(ownerUri: string): Promise<mssql.AssessmentResult | undefined> { async getAssessments(ownerUri: string, databases: string[]): Promise<mssql.AssessmentResult | undefined> {
let params: contracts.SqlMigrationAssessmentParams = { ownerUri: ownerUri }; let params: contracts.SqlMigrationAssessmentParams = { ownerUri: ownerUri, databases: databases };
try { try {
return this.client.sendRequest(contracts.GetSqlMigrationAssessmentItemsRequest.type, params); return this.client.sendRequest(contracts.GetSqlMigrationAssessmentItemsRequest.type, params);
} }

View File

@@ -2,7 +2,7 @@
"name": "sql-migration", "name": "sql-migration",
"displayName": "%displayName%", "displayName": "%displayName%",
"description": "%description%", "description": "%description%",
"version": "0.1.3", "version": "0.1.4",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt", "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",

View File

@@ -464,6 +464,11 @@ export const SOURCE_CREDENTIALS = localize('sql.migration.source.credentials', "
export const ENTER_YOUR_SQL_CREDS = localize('sql.migration.enter.your.sql.cred', "Enter the credential for source SQL Server instance. This credential will be used while migrating database(s) to Azure SQL."); export const ENTER_YOUR_SQL_CREDS = localize('sql.migration.enter.your.sql.cred', "Enter the credential for source SQL Server instance. This credential will be used while migrating database(s) to Azure SQL.");
export const SERVER = localize('sql.migration.server', "Server"); export const SERVER = localize('sql.migration.server', "Server");
export const USERNAME = localize('sql.migration.username', "Username"); export const USERNAME = localize('sql.migration.username', "Username");
export const SIZE = localize('sql.migration.size', "Size (MB)");
export const LAST_BACKUP = localize('sql.migration.last.backup', "Last backup");
export const DATABASE_FOR_MIGRATION = localize('sql.migration.database.migration', "Databases for migration");
export const DATABASE_MIGRATE_TEXT = localize('sql.migrate.text', "Select database(s) that you want to migrate to Azure SQL");
export const OFFLINE_CAPS = localize('sql.migration.offline.caps', "OFFLINE");
//Assessment Dialog //Assessment Dialog
export const ISSUES = localize('sql.migration.issues', "Issues"); export const ISSUES = localize('sql.migration.issues', "Issues");
@@ -493,6 +498,9 @@ export function IMPACT_OBJECT_NAME(objectName?: string): string {
export function DATABASES(selectedCount: number, totalCount: number): string { export function DATABASES(selectedCount: number, totalCount: number): string {
return localize('sql.migration.databases', "Databases ({0}/{1})", selectedCount, totalCount); return localize('sql.migration.databases', "Databases ({0}/{1})", selectedCount, totalCount);
} }
export function DATABASES_SELECTED(selectedCount: number, totalCount: number): string {
return localize('sql.migration.databases.selected', "{0}/{1} Databases Selected", selectedCount, totalCount);
}
export function ISSUES_COUNT(totalCount: number): string { export function ISSUES_COUNT(totalCount: number): string {
return localize('sql.migration.issues.count', "Issues ({0})", totalCount); return localize('sql.migration.issues.count', "Issues ({0})", totalCount);
} }

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { SqlMigrationAssessmentResultItem, SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql'; import { SqlMigrationAssessmentResultItem, SqlMigrationImpactedObjectInfo } from '../../../../mssql/src/mssql';
import { IconPath, IconPathHelper } from '../../constants/iconPathHelper';
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine'; import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine';
import * as constants from '../../constants/strings'; import * as constants from '../../constants/strings';
import { debounce } from '../../api/utils'; import { debounce } from '../../api/utils';
@@ -128,7 +127,7 @@ export class SqlDatabaseTree {
'font-weight': 'bold', 'font-weight': 'bold',
'margin': '0px 8px 0px 36px' 'margin': '0px 8px 0px 36px'
}, },
value: constants.DATABASES(this.selectedDbs.length, this._model._serverDatabases.length) value: constants.DATABASES(0, this._model._databaseAssessment.length)
}).component(); }).component();
return this._databaseCount; return this._databaseCount;
} }
@@ -146,22 +145,22 @@ export class SqlDatabaseTree {
{ {
displayName: '', displayName: '',
valueType: azdata.DeclarativeDataType.boolean, valueType: azdata.DeclarativeDataType.boolean,
width: 20, width: 10,
isReadOnly: false, isReadOnly: false,
showCheckAll: true, showCheckAll: true,
headerCssStyles: headerLeft, headerCssStyles: headerLeft,
}, },
{ {
displayName: constants.DATABASE, displayName: constants.DATABASE,
valueType: azdata.DeclarativeDataType.component, valueType: azdata.DeclarativeDataType.string,
width: 100, width: 95,
isReadOnly: true, isReadOnly: true,
headerCssStyles: headerLeft headerCssStyles: headerLeft
}, },
{ {
displayName: constants.ISSUES, displayName: constants.ISSUES,
valueType: azdata.DeclarativeDataType.string, valueType: azdata.DeclarativeDataType.string,
width: 30, width: 45,
isReadOnly: true, isReadOnly: true,
headerCssStyles: headerRight, headerCssStyles: headerRight,
} }
@@ -170,7 +169,7 @@ export class SqlDatabaseTree {
).component(); ).component();
this._databaseTable.onDataChanged(() => { this._databaseTable.onDataChanged(() => {
this._databaseCount.updateProperties({ this._databaseCount.updateProperties({
'value': constants.DATABASES(this.selectedDbs().length, this._model._serverDatabases.length) 'value': constants.DATABASES(this.selectedDbs().length, this._model._databaseAssessment.length)
}); });
}); });
this._databaseTable.onRowSelected(async (e) => { this._databaseTable.onRowSelected(async (e) => {
@@ -224,8 +223,7 @@ export class SqlDatabaseTree {
if (this._databaseTableValues && value?.length > 0) { if (this._databaseTableValues && value?.length > 0) {
const filter: number[] = []; const filter: number[] = [];
this._databaseTableValues.forEach((row, index) => { this._databaseTableValues.forEach((row, index) => {
const flexContainer: azdata.FlexContainer = row[1]?.value as azdata.FlexContainer; const textComponent: azdata.TextComponent = row[1] as azdata.TextComponent;
const textComponent: azdata.TextComponent = flexContainer.items[1] as azdata.TextComponent;
const cellText = textComponent.value?.toLowerCase(); const cellText = textComponent.value?.toLowerCase();
const searchText: string = value.toLowerCase(); const searchText: string = value.toLowerCase();
if (cellText?.includes(searchText)) { if (cellText?.includes(searchText)) {
@@ -242,20 +240,23 @@ export class SqlDatabaseTree {
private createInstanceComponent(): azdata.DivContainer { private createInstanceComponent(): azdata.DivContainer {
this._instanceTable = this._view.modelBuilder.declarativeTable().withProps( this._instanceTable = this._view.modelBuilder.declarativeTable().withProps(
{ {
CSSStyles: {
'table-layout': 'fixed'
},
width: 200,
enableRowSelection: true, enableRowSelection: true,
width: 170,
columns: [ columns: [
{ {
displayName: constants.INSTANCE, displayName: constants.INSTANCE,
valueType: azdata.DeclarativeDataType.component, valueType: azdata.DeclarativeDataType.string,
width: 130, width: 105,
isReadOnly: true, isReadOnly: true,
headerCssStyles: headerLeft headerCssStyles: headerLeft
}, },
{ {
displayName: constants.WARNINGS, displayName: constants.WARNINGS,
valueType: azdata.DeclarativeDataType.string, valueType: azdata.DeclarativeDataType.string,
width: 30, width: 45,
isReadOnly: true, isReadOnly: true,
headerCssStyles: headerRight headerCssStyles: headerRight
} }
@@ -264,6 +265,7 @@ export class SqlDatabaseTree {
const instanceContainer = this._view.modelBuilder.divContainer().withItems([this._instanceTable]).withProps({ const instanceContainer = this._view.modelBuilder.divContainer().withItems([this._instanceTable]).withProps({
CSSStyles: { CSSStyles: {
'width': '200px',
'margin': '19px 8px 0px 34px' 'margin': '19px 8px 0px 34px'
} }
}).component(); }).component();
@@ -279,7 +281,7 @@ export class SqlDatabaseTree {
}); });
this._recommendation.value = constants.WARNINGS_DETAILS; this._recommendation.value = constants.WARNINGS_DETAILS;
this._recommendationTitle.value = constants.WARNINGS_COUNT(this._activeIssues.length); this._recommendationTitle.value = constants.WARNINGS_COUNT(this._activeIssues.length);
if (this._model._targetType === MigrationTargetType.SQLMI) { if (this._targetType === MigrationTargetType.SQLMI) {
await this.refreshResults(); await this.refreshResults();
} }
}); });
@@ -362,7 +364,7 @@ export class SqlDatabaseTree {
private createNoIssuesText(): azdata.FlexContainer { private createNoIssuesText(): azdata.FlexContainer {
let message: azdata.TextComponent; let message: azdata.TextComponent;
if (this._model._targetType === MigrationTargetType.SQLVM) { if (this._targetType === MigrationTargetType.SQLVM) {
message = this._view.modelBuilder.text().withProps({ message = this._view.modelBuilder.text().withProps({
value: constants.NO_ISSUES_FOUND_VM, value: constants.NO_ISSUES_FOUND_VM,
CSSStyles: { CSSStyles: {
@@ -740,7 +742,7 @@ export class SqlDatabaseTree {
} }
public async refreshResults(): Promise<void> { public async refreshResults(): Promise<void> {
if (this._model._targetType === MigrationTargetType.SQLMI) { if (this._targetType === MigrationTargetType.SQLMI) {
if (this._activeIssues.length === 0) { if (this._activeIssues.length === 0) {
/// show no issues here /// show no issues here
this._assessmentsTable.updateCssStyles({ this._assessmentsTable.updateCssStyles({
@@ -810,8 +812,7 @@ export class SqlDatabaseTree {
public async initialize(): Promise<void> { public async initialize(): Promise<void> {
let instanceTableValues: azdata.DeclarativeTableCellValue[][] = []; let instanceTableValues: azdata.DeclarativeTableCellValue[][] = [];
this._databaseTableValues = []; this._databaseTableValues = [];
const excludedDatabases = ['master', 'msdb', 'tempdb', 'model']; this._dbNames = this._model._databaseAssessment;
this._dbNames = (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 selectedDbs = (this._targetType === MigrationTargetType.SQLVM) ? this._model._vmDbs : this._model._miDbs;
this._serverName = (await this._model.getSourceConnectionProfile()).serverName; this._serverName = (await this._model.getSourceConnectionProfile()).serverName;
@@ -819,7 +820,8 @@ export class SqlDatabaseTree {
instanceTableValues = [ instanceTableValues = [
[ [
{ {
value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName), // value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName),
value: this._serverName,
style: styleLeft style: styleLeft
}, },
{ {
@@ -836,7 +838,8 @@ export class SqlDatabaseTree {
style: styleLeft style: styleLeft
}, },
{ {
value: this.createIconTextCell(IconPathHelper.sqlDatabaseLogo, db), // value: this.createIconTextCell(IconPathHelper.sqlDatabaseLogo, db),
value: db,
style: styleLeft style: styleLeft
}, },
{ {
@@ -850,7 +853,8 @@ export class SqlDatabaseTree {
instanceTableValues = [ instanceTableValues = [
[ [
{ {
value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName), // value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName),
value: this._serverName,
style: styleLeft style: styleLeft
}, },
{ {
@@ -877,7 +881,8 @@ export class SqlDatabaseTree {
enabled: selectable enabled: selectable
}, },
{ {
value: this.createIconTextCell((selectable) ? IconPathHelper.sqlDatabaseLogo : IconPathHelper.sqlDatabaseWarningLogo, db.name), // value: this.createIconTextCell((selectable) ? IconPathHelper.sqlDatabaseLogo : IconPathHelper.sqlDatabaseWarningLogo, db.name),
value: db.name,
style: styleLeft style: styleLeft
}, },
{ {
@@ -892,41 +897,4 @@ export class SqlDatabaseTree {
await this._databaseTable.setDataValues(this._databaseTableValues); await this._databaseTable.setDataValues(this._databaseTableValues);
} }
private createIconTextCell(icon: IconPath, text: string): azdata.FlexContainer {
const iconComponent = this._view.modelBuilder.image().withProps({
iconPath: icon,
iconWidth: '16px',
iconHeight: '16px',
width: '20px',
height: '20px'
}).component();
const textComponent = this._view.modelBuilder.text().withProps({
value: text,
title: text,
CSSStyles: {
'margin': '0px',
'width': '110px'
}
}).component();
const cellContainer = this._view.modelBuilder.flexContainer().withProps({
CSSStyles: {
'justify-content': 'left'
}
}).component();
cellContainer.addItem(iconComponent, {
flex: '0',
CSSStyles: {
'width': '32px'
}
});
cellContainer.addItem(textComponent, {
CSSStyles: {
'width': 'auto'
}
});
return cellContainer;
}
} }

View File

@@ -102,6 +102,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _authenticationType!: MigrationSourceAuthenticationType; public _authenticationType!: MigrationSourceAuthenticationType;
public _sqlServerUsername!: string; public _sqlServerUsername!: string;
public _sqlServerPassword!: string; public _sqlServerPassword!: string;
public _databaseAssessment!: string[];
public _subscriptions!: azureResource.AzureResourceSubscription[]; public _subscriptions!: azureResource.AzureResourceSubscription[];
@@ -120,7 +121,6 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _blobContainers!: azureResource.BlobContainer[]; public _blobContainers!: azureResource.BlobContainer[];
public _refreshNetworkShareLocation!: azureResource.BlobContainer[]; public _refreshNetworkShareLocation!: azureResource.BlobContainer[];
public _targetDatabaseNames!: string[]; public _targetDatabaseNames!: string[];
public _serverDatabases!: string[];
public _sqlMigrationServiceResourceGroup!: string; public _sqlMigrationServiceResourceGroup!: string;
public _sqlMigrationService!: SqlMigrationService; public _sqlMigrationService!: SqlMigrationService;
@@ -138,6 +138,13 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _targetType!: MigrationTargetType; public _targetType!: MigrationTargetType;
public refreshDatabaseBackupPage!: boolean; public refreshDatabaseBackupPage!: boolean;
public excludeDbs: string[] = [
'master',
'tempdb',
'msdb',
'model'
];
constructor( constructor(
private readonly _extensionContext: vscode.ExtensionContext, private readonly _extensionContext: vscode.ExtensionContext,
private readonly _sourceConnectionId: string, private readonly _sourceConnectionId: string,
@@ -162,28 +169,25 @@ export class MigrationStateModel implements Model, vscode.Disposable {
this._currentState = newState; this._currentState = newState;
this._stateChangeEventEmitter.fire({ oldState, newState: this.currentState }); this._stateChangeEventEmitter.fire({ oldState, newState: this.currentState });
} }
public async getDatabases(): Promise<string[]> {
let temp = await azdata.connection.listDatabases(this.sourceConnectionId);
let finalResult = temp.filter((name) => !this.excludeDbs.includes(name));
return finalResult;
}
public async getServerAssessments(): Promise<ServerAssessement> { public async getDatabaseAssessments(): Promise<ServerAssessement> {
const excludeDbs: string[] = [
'master',
'tempdb',
'msdb',
'model'
];
const ownerUri = await azdata.connection.getUriForConnection(this.sourceConnectionId); const ownerUri = await azdata.connection.getUriForConnection(this.sourceConnectionId);
// stress test backend & dialog component
const assessmentResults = await this.migrationService.getAssessments( const assessmentResults = await this.migrationService.getAssessments(
ownerUri ownerUri,
this._databaseAssessment
); );
this._serverDatabases = await (await azdata.connection.listDatabases(this.sourceConnectionId)).filter((name) => !excludeDbs.includes(name)); const dbAssessments = assessmentResults?.assessmentResult.databases.filter(d => !this.excludeDbs.includes(d.name)).map(d => {
const dbAssessments = assessmentResults?.assessmentResult.databases.filter(d => !excludeDbs.includes(d.name)).map(d => {
return { return {
name: d.name, name: d.name,
issues: d.items.filter(i => i.appliesToMigrationTargetPlatform === MigrationTargetType.SQLMI) ?? [] issues: d.items.filter(i => i.appliesToMigrationTargetPlatform === MigrationTargetType.SQLMI) ?? []
}; };
}); });
this._assessmentResults = { this._assessmentResults = {
issues: assessmentResults?.assessmentResult.items?.filter(i => i.appliesToMigrationTargetPlatform === MigrationTargetType.SQLMI) ?? [], issues: assessmentResults?.assessmentResult.items?.filter(i => i.appliesToMigrationTargetPlatform === MigrationTargetType.SQLMI) ?? [],
databaseAssessments: dbAssessments! ?? [] databaseAssessments: dbAssessments! ?? []
@@ -192,7 +196,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
return this._assessmentResults; return this._assessmentResults;
} }
public getDatabaseAssessments(databaseName: string): mssql.SqlMigrationAssessmentResultItem[] | undefined { public findDatabaseAssessments(databaseName: string): mssql.SqlMigrationAssessmentResultItem[] | undefined {
return this._assessmentResults.databaseAssessments.find(databaseAsssessment => databaseAsssessment.name === databaseName)?.issues; return this._assessmentResults.databaseAssessments.find(databaseAsssessment => databaseAsssessment.name === databaseName)?.issues;
} }

View File

@@ -0,0 +1,312 @@
/*---------------------------------------------------------------------------------------------
* 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';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import { IconPath, IconPathHelper } from '../constants/iconPathHelper';
import { debounce } from '../api/utils';
const headerLeft: azdata.CssStyles = {
'border': 'none',
'text-align': 'left',
'white-space': 'nowrap',
'text-overflow': 'ellipsis',
'overflow': 'hidden',
'border-bottom': '1px solid'
};
const styleLeft: azdata.CssStyles = {
'border': 'none',
'text-align': 'left',
'white-space': 'nowrap',
'text-overflow': 'ellipsis',
'overflow': 'hidden',
};
const styleCenter: azdata.CssStyles = {
'border': 'none',
'text-align': 'center',
'white-space': 'nowrap',
'text-overflow': 'ellipsis',
'overflow': 'hidden',
};
export class DatabaseSelectorPage extends MigrationWizardPage {
private _view!: azdata.ModelView;
private _databaseSelectorTable!: azdata.DeclarativeTableComponent;
private _dbNames!: string[];
private _dbCount!: azdata.TextComponent;
private _databaseTableValues!: azdata.DeclarativeTableCellValue[][];
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.SOURCE_CONFIGURATION, 'MigrationModePage'), migrationStateModel);
}
protected async registerContent(view: azdata.ModelView): Promise<void> {
this._view = view;
const flex = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'row',
height: '100%',
width: '100%'
}).component();
flex.addItem(await this.createRootContainer(view), { flex: '1 1 auto' });
await view.initializeModel(flex);
}
public async onPageEnter(): Promise<void> {
this.wizard.registerNavigationValidator((pageChangeInfo) => {
return true;
});
}
public async onPageLeave(): Promise<void> {
this.migrationStateModel._databaseAssessment = this.selectedDbs();
this.wizard.registerNavigationValidator((pageChangeInfo) => {
return true;
});
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
}
private createSearchComponent(): azdata.DivContainer {
let resourceSearchBox = this._view.modelBuilder.inputBox().withProps({
stopEnterPropagation: true,
placeHolder: constants.SEARCH,
width: 200
}).component();
resourceSearchBox.onTextChanged(value => this._filterTableList(value));
const searchContainer = this._view.modelBuilder.divContainer().withItems([resourceSearchBox]).withProps({
CSSStyles: {
'width': '200px',
'margin': '10px 8px 0px 0px'
}
}).component();
return searchContainer;
}
@debounce(500)
private _filterTableList(value: string): void {
if (this._databaseTableValues && value?.length > 0) {
const filter: number[] = [];
this._databaseTableValues.forEach((row, index) => {
const flexContainer: azdata.FlexContainer = row[1]?.value as azdata.FlexContainer;
const textComponent: azdata.TextComponent = flexContainer.items[1] as azdata.TextComponent;
const cellText = textComponent.value?.toLowerCase();
const searchText: string = value.toLowerCase();
if (cellText?.includes(searchText)) {
filter.push(index);
}
});
this._databaseSelectorTable.setFilter(filter);
} else {
this._databaseSelectorTable.setFilter(undefined);
}
}
public async createRootContainer(view: azdata.ModelView): Promise<azdata.FlexContainer> {
const providerId = (await this.migrationStateModel.getSourceConnectionProfile()).providerId;
const metaDataService = azdata.dataprotocol.getProvider<azdata.MetadataProvider>(providerId, azdata.DataProviderType.MetadataProvider);
const ownerUri = await azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId);
const results = <azdata.DatabaseInfo[]>await metaDataService.getDatabases(ownerUri);
const excludeDbs: string[] = [
'master',
'tempdb',
'msdb',
'model'
];
this._dbNames = [];
let finalResult = results.filter((db) => !excludeDbs.includes(db.options.name));
finalResult.sort((a, b) => a.options.name.localeCompare(b.options.name));
this._databaseTableValues = [];
for (let index in finalResult) {
let selectable = true;
if (constants.OFFLINE_CAPS.includes(finalResult[index].options.state)) {
selectable = false;
}
this._databaseTableValues.push([
{
value: false,
style: styleCenter,
enabled: selectable
},
{
value: this.createIconTextCell(IconPathHelper.sqlDatabaseLogo, finalResult[index].options.name),
style: styleLeft
},
{
value: `${finalResult[index].options.state}`,
style: styleLeft
},
{
value: `${finalResult[index].options.sizeInMB}`,
style: styleLeft
},
{
value: `${finalResult[index].options.lastBackup}`,
style: styleLeft
}
]);
this._dbNames.push(finalResult[index].options.name);
}
const title = this._view.modelBuilder.text().withProps({
value: constants.DATABASE_FOR_MIGRATION,
CSSStyles: {
'font-size': '28px',
'line-size': '19px',
'margin': '16px 0px 20px 0px'
}
}).component();
const text = this._view.modelBuilder.text().withProps({
value: constants.DATABASE_MIGRATE_TEXT,
CSSStyles: {
'font-size': '13px',
'line-size': '19px',
'margin': '10px 0px 0px 0px'
}
}).component();
this._dbCount = this._view.modelBuilder.text().withProps({
value: constants.DATABASES_SELECTED(this.selectedDbs.length, this._databaseTableValues.length),
CSSStyles: {
'font-size': '13px',
'line-size': '19px',
'margin': '10px 0px 0px 0px'
}
}).component();
this._databaseSelectorTable = this._view.modelBuilder.declarativeTable().withProps(
{
enableRowSelection: true,
width: '800px',
CSSStyles: {
'table-layout': 'fixed',
'border': 'none'
},
columns: [
{
displayName: '',
valueType: azdata.DeclarativeDataType.boolean,
width: 1,
isReadOnly: false,
showCheckAll: true,
headerCssStyles: headerLeft,
},
{
displayName: constants.DATABASE,
valueType: azdata.DeclarativeDataType.component,
width: 100,
isReadOnly: true,
headerCssStyles: headerLeft
},
{
displayName: constants.STATUS,
valueType: azdata.DeclarativeDataType.string,
width: 20,
isReadOnly: true,
headerCssStyles: headerLeft
},
{
displayName: constants.SIZE,
valueType: azdata.DeclarativeDataType.string,
width: 30,
isReadOnly: true,
headerCssStyles: headerLeft
},
{
displayName: constants.LAST_BACKUP,
valueType: azdata.DeclarativeDataType.string,
width: 50,
isReadOnly: true,
headerCssStyles: headerLeft
}
]
}
).component();
await this._databaseSelectorTable.setDataValues(this._databaseTableValues);
this._databaseSelectorTable.onDataChanged(() => {
this._dbCount.updateProperties({
'value': constants.DATABASES_SELECTED(this.selectedDbs().length, this._databaseTableValues.length)
});
});
const flex = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
height: '100%',
width: '100%'
}).withProps({
CSSStyles: {
'margin': '0px 0px 0px 28px'
}
}).component();
flex.addItem(title, { flex: '0 0 auto' });
flex.addItem(text, { flex: '0 0 auto' });
flex.addItem(this.createSearchComponent(), { flex: '0 0 auto' });
flex.addItem(this._dbCount, { flex: '0 0 auto' });
flex.addItem(this._databaseSelectorTable);
return flex;
// insert names of databases into table
}
public selectedDbs(): string[] {
let result: string[] = [];
this._databaseSelectorTable.dataValues?.forEach((arr, index) => {
if (arr[0].value === true) {
result.push(this._dbNames[index]);
}
});
return result;
}
private createIconTextCell(icon: IconPath, text: string): azdata.FlexContainer {
const iconComponent = this._view.modelBuilder.image().withProps({
iconPath: icon,
iconWidth: '16px',
iconHeight: '16px',
width: '20px',
height: '20px'
}).component();
const textComponent = this._view.modelBuilder.text().withProps({
value: text,
title: text,
CSSStyles: {
'margin': '0px',
'width': '110px'
}
}).component();
const cellContainer = this._view.modelBuilder.flexContainer().withProps({
CSSStyles: {
'justify-content': 'left'
}
}).component();
cellContainer.addItem(iconComponent, {
flex: '0',
CSSStyles: {
'width': '32px'
}
});
cellContainer.addItem(textComponent, {
CSSStyles: {
'width': 'auto'
}
});
return cellContainer;
}
}

View File

@@ -403,13 +403,13 @@ export class SKURecommendationPage extends MigrationWizardPage {
private changeTargetType(newTargetType: string) { private changeTargetType(newTargetType: string) {
if (newTargetType === MigrationTargetType.SQLMI) { if (newTargetType === MigrationTargetType.SQLMI) {
this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI; this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI;
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel._miDbs.length, this.migrationStateModel._serverDatabases.length); this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel._miDbs.length, this.migrationStateModel._databaseAssessment.length);
this.migrationStateModel._targetType = MigrationTargetType.SQLMI; this.migrationStateModel._targetType = MigrationTargetType.SQLMI;
this._azureSubscriptionText.value = constants.SELECT_AZURE_MI; this._azureSubscriptionText.value = constants.SELECT_AZURE_MI;
this.migrationStateModel._migrationDbs = this.migrationStateModel._miDbs; this.migrationStateModel._migrationDbs = this.migrationStateModel._miDbs;
} else { } else {
this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_VM; this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_VM;
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel._vmDbs.length, this.migrationStateModel._serverDatabases.length); this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel._vmDbs.length, this.migrationStateModel._databaseAssessment.length);
this.migrationStateModel._targetType = MigrationTargetType.SQLVM; this.migrationStateModel._targetType = MigrationTargetType.SQLVM;
this._azureSubscriptionText.value = constants.SELECT_AZURE_VM; this._azureSubscriptionText.value = constants.SELECT_AZURE_VM;
this.migrationStateModel._migrationDbs = this.migrationStateModel._vmDbs; this.migrationStateModel._migrationDbs = this.migrationStateModel._vmDbs;
@@ -424,7 +424,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
const serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName; const serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName); this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName);
try { try {
await this.migrationStateModel.getServerAssessments(); await this.migrationStateModel.getDatabaseAssessments();
this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults.databaseAssessments.length); this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults.databaseAssessments.length);
} catch (e) { } catch (e) {
console.log(e); console.log(e);

View File

@@ -15,6 +15,7 @@ import { AccountsSelectionPage } from './accountsSelectionPage';
import { IntergrationRuntimePage } from './integrationRuntimePage'; import { IntergrationRuntimePage } from './integrationRuntimePage';
import { SummaryPage } from './summaryPage'; import { SummaryPage } from './summaryPage';
import { MigrationModePage } from './migrationModePage'; import { MigrationModePage } from './migrationModePage';
import { DatabaseSelectorPage } from './databaseSelectorPage';
export const WIZARD_INPUT_COMPONENT_WIDTH = '600px'; export const WIZARD_INPUT_COMPONENT_WIDTH = '600px';
export class WizardController { export class WizardController {
@@ -38,6 +39,7 @@ export class WizardController {
wizard.generateScriptButton.hidden = true; wizard.generateScriptButton.hidden = true;
const skuRecommendationPage = new SKURecommendationPage(wizard, stateModel); const skuRecommendationPage = new SKURecommendationPage(wizard, stateModel);
const migrationModePage = new MigrationModePage(wizard, stateModel); const migrationModePage = new MigrationModePage(wizard, stateModel);
const databaseSelectorPage = new DatabaseSelectorPage(wizard, stateModel);
const azureAccountsPage = new AccountsSelectionPage(wizard, stateModel); const azureAccountsPage = new AccountsSelectionPage(wizard, stateModel);
const databaseBackupPage = new DatabaseBackupPage(wizard, stateModel); const databaseBackupPage = new DatabaseBackupPage(wizard, stateModel);
const integrationRuntimePage = new IntergrationRuntimePage(wizard, stateModel); const integrationRuntimePage = new IntergrationRuntimePage(wizard, stateModel);
@@ -45,6 +47,7 @@ export class WizardController {
const pages: MigrationWizardPage[] = [ const pages: MigrationWizardPage[] = [
azureAccountsPage, azureAccountsPage,
databaseSelectorPage,
skuRecommendationPage, skuRecommendationPage,
migrationModePage, migrationModePage,
databaseBackupPage, databaseBackupPage,