mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-06 09:35:41 -05:00
[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:
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user