[SQL Migration] Add support for assessing XEvent session files (#22210)

* Template

* Refactor

* Update strings

* Clean up

* Add clear button

* Clean up

* Fix typo and use aka.ms link

* Refactor to use GroupContainer

* Remove dialog and clean up common strings

* Fix previous/forward behavior

* Make group container default to collapsed

* Clean up

* Slightly reword string

* Add https to aka.ms link
This commit is contained in:
Raymond Truong
2023-05-02 14:45:13 +00:00
committed by GitHub
parent 1f3a514c90
commit 6de9c5e1ae
9 changed files with 104 additions and 12 deletions

View File

@@ -76,6 +76,12 @@ export const RUN_VALIDATION = localize('sql.migration.run.validation', "Run vali
export const DATABASE_FOR_ASSESSMENT_PAGE_TITLE = localize('sql.migration.database.assessment.title', "Databases for assessment");
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.");
// XEvents assessment
export const XEVENTS_ASSESSMENT_TITLE = localize('sql.migration.database.assessment.xevents.title', "Assess extended event sessions");
export const XEVENTS_ASSESSMENT_DESCRIPTION = localize('sql.migration.database.assessment.xevents.description', "For the selected databases, optionally provide extended event session files to assess ad-hoc or dynamic SQL queries or any DML statements initiated through the application data layer. {0}");
export const XEVENTS_ASSESSMENT_HELPLINK = localize('sql.migration.database.assessment.xevents.link', "Learn more");
export const XEVENTS_ASSESSMENT_OPEN_FOLDER = localize('sql.migration.database.assessment.xevents.instructions', "Select a folder where extended events session files (.xel and .xem) are stored");
// Assessment results and recommendations
export const ASSESSMENT_RESULTS_AND_RECOMMENDATIONS_PAGE_TITLE = localize('sql.migration.assessment.results.and.recommendations.title', "Assessment results and recommendations");
export const ASSESSMENT_BLOCKING_ISSUE_TITLE = localize('sql.migration.assessments.blocking.issue', 'This is a blocking issue that will prevent the database migration from succeeding.');
@@ -194,8 +200,6 @@ export const AZURE_RECOMMENDATION_OPEN_EXISTING = localize('sql.migration.sku.az
export const AZURE_RECOMMENDATION_COLLECT_DATA_FOLDER = localize('sql.migration.sku.azureRecommendation.collectDataSelectFolder.instructions', "Select a folder on your local drive where performance data will be saved");
export const AZURE_RECOMMENDATION_OPEN_EXISTING_FOLDER = localize('sql.migration.sku.azureRecommendation.openExistingSelectFolder.instructions', "Select a folder on your local drive where previously collected performance data was saved");
export const FOLDER_NAME = localize('sql.migration.azureRecommendation.folder.name', "Folder name");
export const BROWSE = localize('sql.migration.azureRecommendation.browse', "Browse");
export const OPEN = localize('sql.migration.azureRecommendation.open', "Open");
export const VIEW_DETAILS = localize('sql.migration.sku.viewDetails', "View details");
export function ASSESSED_DBS(totalDbs: number): string {
@@ -926,6 +930,10 @@ export const COPY_THROUGHPUT = localize('sql.migration.copy.throughput', "Copy t
export const NEW_SUPPORT_REQUEST = localize('sql.migration.newSupportRequest', "New support request");
export const IMPACT = localize('sql.migration.impact', "Impact");
export const ALL_FIELDS_REQUIRED = localize('sql.migration.all.fields.required', 'All fields are required.');
export const CLEAR = localize('sql.migration.clear', "Clear");
export const SELECT = localize('sql.migration.select', "Select");
export const BROWSE = localize('sql.migration.browse', "Browse");
export const OPEN = localize('sql.migration.open', "Open");
//Summary Page
export const START_MIGRATION_TEXT = localize('sql.migration.start.migration.button', "Start migration");
@@ -1170,7 +1178,6 @@ export const OPEN_MIGRATION_DETAILS_ERROR = localize('sql.migration.open.migrati
export const OPEN_MIGRATION_TARGET_ERROR = localize('sql.migration.open.migration.target.error', "Error opening migration target");
export const OPEN_MIGRATION_SERVICE_ERROR = localize('sql.migration.open.migration.service.error', "Error opening migration service dialog");
export const LOAD_MIGRATION_LIST_ERROR = localize('sql.migration.load.migration.list.error', "Error loading migrations list");
export const ERROR_DIALOG_CLEAR_BUTTON_LABEL = localize('sql.migration.error.dialog.clear.button.label', "Clear");
export const ERROR_DIALOG_ARIA_CLICK_VIEW_ERROR_DETAILS = localize('sql.migration.error.aria.view.details', 'Click to view error details');
export interface LookupTable<T> {

View File

@@ -102,7 +102,7 @@ export class DashboardStatusBar implements vscode.Disposable {
450,
'flyout');
dialog.content = [tab];
dialog.okButton.label = loc.ERROR_DIALOG_CLEAR_BUTTON_LABEL;
dialog.okButton.label = loc.CLEAR;
dialog.okButton.focused = true;
dialog.okButton.position = 'left';
dialog.cancelButton.label = loc.CLOSE;

View File

@@ -12,7 +12,7 @@ import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRestart
import { IconPathHelper } from '../constants/iconPathHelper';
import { MigrationNotebookInfo, NotebookPathHelper } from '../constants/notebookPathHelper';
import * as loc from '../constants/strings';
import { SavedAssessmentDialog } from '../dialog/assessmentResults/savedAssessmentDialog';
import { SavedAssessmentDialog } from '../dialog/assessment/savedAssessmentDialog';
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migrationCutoverDialogModel';
import { RestartMigrationDialog } from '../dialog/restartMigration/restartMigrationDialog';

View File

@@ -210,6 +210,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public _nodeNames!: string[];
public _databasesForAssessment!: string[];
public _xEventsFilesFolderPath: string = '';
public _assessmentResults!: ServerAssessment;
public _assessedDatabaseList!: string[];
public _runAssessments: boolean = true;
@@ -400,8 +401,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
public async getDatabaseAssessments(targetType: MigrationTargetType[]): Promise<ServerAssessment> {
const connectionString = await getSourceConnectionString();
try {
const xEventsFilesFolderPath = ''; // to-do: collect by prompting the user in the UI - for now, blank = disabled
const response = (await this.migrationService.getAssessments(connectionString, this._databasesForAssessment, xEventsFilesFolderPath))!;
const response = (await this.migrationService.getAssessments(connectionString, this._databasesForAssessment, this._xEventsFilesFolderPath ?? ''))!;
this._assessmentApiResponse = response;
this._assessedDatabaseList = this._databasesForAssessment.slice();

View File

@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
import * as constants from '../constants/strings';
import { debounce } from '../api/utils';
import { debounce, promptUserForFolder } from '../api/utils';
import * as styles from '../constants/styles';
import { IconPathHelper } from '../constants/iconPathHelper';
import { getDatabasesList, excludeDatabases, SourceDatabaseInfo, getSourceConnectionProfile } from '../api/sqlUtils';
@@ -16,11 +16,16 @@ import { getDatabasesList, excludeDatabases, SourceDatabaseInfo, getSourceConnec
export class DatabaseSelectorPage extends MigrationWizardPage {
private _view!: azdata.ModelView;
private _databaseSelectorTable!: azdata.TableComponent;
private _xEventsGroup!: azdata.GroupContainer;
private _xEventsFolderPickerInput!: azdata.InputBoxComponent;
private _xEventsFilesFolderPath!: string;
private _dbNames!: string[];
private _dbCount!: azdata.TextComponent;
private _databaseTableValues!: any[];
private _disposables: vscode.Disposable[] = [];
private readonly TABLE_WIDTH = 650;
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
super(wizard, azdata.window.createWizardPage(constants.DATABASE_FOR_ASSESSMENT_PAGE_TITLE), migrationStateModel);
}
@@ -61,6 +66,8 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
}
return true;
});
this._xEventsFilesFolderPath = this.migrationStateModel._xEventsFilesFolderPath;
}
public async onPageLeave(): Promise<void> {
@@ -73,11 +80,15 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
// * no prior assessment
// * the prior assessment had an error or
// * the assessed databases list is different from the selected databases list
// * the XEvents path has changed
this.migrationStateModel._runAssessments = !this.migrationStateModel._assessmentResults
|| !!this.migrationStateModel._assessmentResults?.assessmentError
|| assessedDatabases.length === 0
|| assessedDatabases.length !== selectedDatabases.length
|| assessedDatabases.some(db => selectedDatabases.indexOf(db) < 0);
|| assessedDatabases.some(db => selectedDatabases.indexOf(db) < 0)
|| this.migrationStateModel._xEventsFilesFolderPath.toLowerCase() !== this._xEventsFilesFolderPath.toLowerCase();
this.migrationStateModel._xEventsFilesFolderPath = this._xEventsFilesFolderPath;
}
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
@@ -156,8 +167,9 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
this._databaseSelectorTable = this._view.modelBuilder.table()
.withProps({
data: [],
width: 650,
width: this.TABLE_WIDTH,
height: '100%',
CSSStyles: { 'margin-bottom': '12px' },
forceFitColumns: azdata.ColumnSizingMode.ForceFit,
columns: [
<azdata.CheckboxColumn>{
@@ -212,18 +224,91 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
// load unfiltered table list and pre-select list of databases saved in state
await this._filterTableList('', this.migrationStateModel._databasesForAssessment);
const xEventsDescription = this._view.modelBuilder.text()
.withProps({
value: constants.XEVENTS_ASSESSMENT_DESCRIPTION,
width: this.TABLE_WIDTH,
CSSStyles: { ...styles.BODY_CSS },
links: [{ text: constants.XEVENTS_ASSESSMENT_HELPLINK, url: 'https://aka.ms/sql-migration-xe-assess' }]
}).component();
const xEventsInstructions = this._view.modelBuilder.text()
.withProps({
value: constants.XEVENTS_ASSESSMENT_OPEN_FOLDER,
width: this.TABLE_WIDTH,
CSSStyles: { ...styles.LABEL_CSS },
}).component();
this._xEventsFolderPickerInput = this._view.modelBuilder.inputBox()
.withProps({
placeHolder: constants.FOLDER_NAME,
readOnly: true,
width: 460,
ariaLabel: constants.XEVENTS_ASSESSMENT_OPEN_FOLDER
}).component();
this._disposables.push(
this._xEventsFolderPickerInput.onTextChanged(async (value) => {
if (value) {
this._xEventsFilesFolderPath = value.trim();
}
}));
const xEventsFolderPickerButton = this._view.modelBuilder.button()
.withProps({
label: constants.OPEN,
width: 80,
}).component();
this._disposables.push(
xEventsFolderPickerButton.onDidClick(
async () => this._xEventsFolderPickerInput.value = await promptUserForFolder()));
const xEventsFolderPickerClearButton = this._view.modelBuilder.button()
.withProps({
label: constants.CLEAR,
width: 80,
}).component();
this._disposables.push(
xEventsFolderPickerClearButton.onDidClick(
async () => {
this._xEventsFolderPickerInput.value = '';
this._xEventsFilesFolderPath = '';
}));
const xEventsFolderPickerContainer = this._view.modelBuilder.flexContainer()
.withProps({
CSSStyles: { 'flex-direction': 'row', 'align-items': 'left' }
}).withItems([
this._xEventsFolderPickerInput,
xEventsFolderPickerButton,
xEventsFolderPickerClearButton
]).component();
this._xEventsGroup = this._view.modelBuilder.groupContainer()
.withLayout({
header: constants.XEVENTS_ASSESSMENT_TITLE,
collapsible: true,
collapsed: true
}).withItems([
xEventsDescription,
xEventsInstructions,
xEventsFolderPickerContainer
]).component();
const flex = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
height: '100%',
height: '65%',
}).withProps({
CSSStyles: {
'margin': '0px 28px 0px 28px'
}
}).component();
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);
flex.addItem(this._xEventsGroup, { flex: '0 0 auto' });
return flex;
}

View File

@@ -10,7 +10,7 @@ import { MigrationTargetType } from '../api/utils';
import * as contracts from '../service/contracts';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, PerformanceDataSourceOptions, StateChangeEvent, AssessmentRuleId } from '../models/stateMachine';
import { AssessmentResultsDialog } from '../dialog/assessmentResults/assessmentResultsDialog';
import { AssessmentResultsDialog } from '../dialog/assessment/assessmentResultsDialog';
import { SkuRecommendationResultsDialog } from '../dialog/skuRecommendationResults/skuRecommendationResultsDialog';
import { GetAzureRecommendationDialog } from '../dialog/skuRecommendationResults/getAzureRecommendationDialog';
import * as constants from '../constants/strings';