mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
[SQL Migration] Add buttons to allow saving assessment/recommendation reports (#20212)
* Implement save assessment report * Implement save recommendation report
This commit is contained in:
2
extensions/mssql/src/mssql.d.ts
vendored
2
extensions/mssql/src/mssql.d.ts
vendored
@@ -541,6 +541,7 @@ declare module 'mssql' {
|
|||||||
sqlMiRecommendationResults: PaaSSkuRecommendationResultItem[];
|
sqlMiRecommendationResults: PaaSSkuRecommendationResultItem[];
|
||||||
sqlVmRecommendationResults: IaaSSkuRecommendationResultItem[];
|
sqlVmRecommendationResults: IaaSSkuRecommendationResultItem[];
|
||||||
instanceRequirements: SqlInstanceRequirements;
|
instanceRequirements: SqlInstanceRequirements;
|
||||||
|
skuRecommendationReportPaths: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// SKU recommendation enums, mirrored from Microsoft.SqlServer.Migration.SkuRecommendation
|
// SKU recommendation enums, mirrored from Microsoft.SqlServer.Migration.SkuRecommendation
|
||||||
@@ -785,6 +786,7 @@ declare module 'mssql' {
|
|||||||
assessmentResult: ServerAssessmentProperties;
|
assessmentResult: ServerAssessmentProperties;
|
||||||
rawAssessmentResult: any;
|
rawAssessmentResult: any;
|
||||||
errors: ErrorModel[];
|
errors: ErrorModel[];
|
||||||
|
assessmentReportPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISqlMigrationService {
|
export interface ISqlMigrationService {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { window, Account, accounts, CategoryValue, DropDownComponent, IconPath } from 'azdata';
|
import { window, Account, accounts, CategoryValue, DropDownComponent, IconPath } from 'azdata';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { MigrationStatus, ProvisioningState } from '../models/migrationLocalStorage';
|
import { MigrationStatus, ProvisioningState } from '../models/migrationLocalStorage';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
@@ -865,3 +866,21 @@ export async function getBlobLastBackupFileNamesValues(lastFileNames: azureResou
|
|||||||
}
|
}
|
||||||
return lastFileNamesValues;
|
return lastFileNamesValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function promptUserForFolder(): Promise<string> {
|
||||||
|
let path = '';
|
||||||
|
|
||||||
|
let options: vscode.OpenDialogOptions = {
|
||||||
|
defaultUri: vscode.Uri.file(getUserHome()!),
|
||||||
|
canSelectFiles: false,
|
||||||
|
canSelectFolders: true,
|
||||||
|
canSelectMany: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fileUris = await vscode.window.showOpenDialog(options);
|
||||||
|
if (fileUris && fileUris?.length > 0 && fileUris[0]) {
|
||||||
|
path = fileUris[0].fsPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|||||||
@@ -94,6 +94,14 @@ export function CAN_BE_MIGRATED(eligibleDbs: number, totalDbs: number): string {
|
|||||||
export const ASSESSMENT_MIGRATION_WARNING = localize('sql.migration.assessment.migration.warning', "Databases that are not ready for migration to Azure SQL Managed Instance can be migrated to SQL Server on Azure Virtual Machines.");
|
export const ASSESSMENT_MIGRATION_WARNING = localize('sql.migration.assessment.migration.warning', "Databases that are not ready for migration to Azure SQL Managed Instance can be migrated to SQL Server on Azure Virtual Machines.");
|
||||||
export const DATABASES_TABLE_TILE = localize('sql.migration.databases.table.title', "Databases");
|
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 SQL_SERVER_INSTANCE = localize('sql.migration.sql.server.instance', "SQL Server instance");
|
||||||
|
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");
|
||||||
|
export function SAVE_ASSESSMENT_REPORT_SUCCESS(filePath: string): string {
|
||||||
|
return localize('sql.migration.save.assessment.report.success', "Successfully saved assessment report to {0}.", filePath);
|
||||||
|
}
|
||||||
|
export function SAVE_RECOMMENDATION_REPORT_SUCCESS(filePath: string): string {
|
||||||
|
return localize('sql.migration.save.recommendation.report.success', "Successfully saved recommendation report to {0}.", filePath);
|
||||||
|
}
|
||||||
|
|
||||||
// SKU
|
// SKU
|
||||||
export const AZURE_RECOMMENDATION = localize('sql.migration.sku.recommendation', "Azure recommendation");
|
export const AZURE_RECOMMENDATION = localize('sql.migration.sku.recommendation', "Azure recommendation");
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import { MigrationStateModel, MigrationTargetType } from '../../models/stateMach
|
|||||||
import { SqlDatabaseTree } from './sqlDatabasesTree';
|
import { SqlDatabaseTree } from './sqlDatabasesTree';
|
||||||
import { SqlMigrationImpactedObjectInfo } from 'mssql';
|
import { SqlMigrationImpactedObjectInfo } from 'mssql';
|
||||||
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
|
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
|
||||||
|
import * as constants from '../../constants/strings';
|
||||||
|
import * as utils from '../../api/utils';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import path = require('path');
|
||||||
|
|
||||||
export type Issues = {
|
export type Issues = {
|
||||||
description: string,
|
description: string,
|
||||||
@@ -24,6 +28,8 @@ export class AssessmentResultsDialog {
|
|||||||
private _isOpen: boolean = false;
|
private _isOpen: boolean = false;
|
||||||
private dialog: azdata.window.Dialog | undefined;
|
private dialog: azdata.window.Dialog | undefined;
|
||||||
private _model: MigrationStateModel;
|
private _model: MigrationStateModel;
|
||||||
|
private _saveButton!: azdata.window.Button;
|
||||||
|
private static readonly _assessmentReportName: string = 'SqlAssessmentReport.json';
|
||||||
|
|
||||||
// Dialog Name for Telemetry
|
// Dialog Name for Telemetry
|
||||||
public dialogName: string | undefined;
|
public dialogName: string | undefined;
|
||||||
@@ -71,6 +77,27 @@ export class AssessmentResultsDialog {
|
|||||||
this.dialog.cancelButton.label = AssessmentResultsDialog.CancelButtonText;
|
this.dialog.cancelButton.label = AssessmentResultsDialog.CancelButtonText;
|
||||||
this._disposables.push(this.dialog.cancelButton.onClick(async () => await this.cancel()));
|
this._disposables.push(this.dialog.cancelButton.onClick(async () => await this.cancel()));
|
||||||
|
|
||||||
|
this._saveButton = azdata.window.createButton(
|
||||||
|
constants.SAVE_ASSESSMENT_REPORT,
|
||||||
|
'left');
|
||||||
|
this._disposables.push(
|
||||||
|
this._saveButton.onClick(async () => {
|
||||||
|
const folder = await utils.promptUserForFolder();
|
||||||
|
const destinationFilePath = path.join(folder, AssessmentResultsDialog._assessmentReportName);
|
||||||
|
if (this.model._assessmentReportFilePath) {
|
||||||
|
fs.copyFile(this.model._assessmentReportFilePath, destinationFilePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
} else {
|
||||||
|
void vscode.window.showInformationMessage(constants.SAVE_ASSESSMENT_REPORT_SUCCESS(destinationFilePath));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('assessment report not found');
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this.dialog.customButtons = [this._saveButton];
|
||||||
|
|
||||||
const dialogSetupPromises: Thenable<void>[] = [];
|
const dialogSetupPromises: Thenable<void>[] = [];
|
||||||
|
|
||||||
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export class GetAzureRecommendationDialog {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(browseButton.onDidClick(async (e) => {
|
this._disposables.push(browseButton.onDidClick(async (e) => {
|
||||||
let folder = await this.handleBrowse();
|
let folder = await utils.promptUserForFolder();
|
||||||
this._collectDataFolderInput.value = folder;
|
this._collectDataFolderInput.value = folder;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ export class GetAzureRecommendationDialog {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(openButton.onDidClick(async (e) => {
|
this._disposables.push(openButton.onDidClick(async (e) => {
|
||||||
let folder = await this.handleBrowse();
|
let folder = await utils.promptUserForFolder();
|
||||||
this._openExistingFolderInput.value = folder;
|
this._openExistingFolderInput.value = folder;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -380,23 +380,4 @@ export class GetAzureRecommendationDialog {
|
|||||||
public get isOpen(): boolean {
|
public get isOpen(): boolean {
|
||||||
return this._isOpen;
|
return this._isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TO-DO: add validation
|
|
||||||
private async handleBrowse(): Promise<string> {
|
|
||||||
let path = '';
|
|
||||||
|
|
||||||
let options: vscode.OpenDialogOptions = {
|
|
||||||
defaultUri: vscode.Uri.file(utils.getUserHome()!),
|
|
||||||
canSelectFiles: false,
|
|
||||||
canSelectFolders: true,
|
|
||||||
canSelectMany: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let fileUris = await vscode.window.showOpenDialog(options);
|
|
||||||
if (fileUris && fileUris?.length > 0 && fileUris[0]) {
|
|
||||||
path = fileUris[0].fsPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import { MigrationStateModel, MigrationTargetType } from '../../models/stateMach
|
|||||||
import * as constants from '../../constants/strings';
|
import * as constants from '../../constants/strings';
|
||||||
import * as styles from '../../constants/styles';
|
import * as styles from '../../constants/styles';
|
||||||
import * as mssql from 'mssql';
|
import * as mssql from 'mssql';
|
||||||
|
import * as utils from '../../api/utils';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import path = require('path');
|
||||||
|
|
||||||
export class SkuRecommendationResultsDialog {
|
export class SkuRecommendationResultsDialog {
|
||||||
|
|
||||||
@@ -23,6 +26,7 @@ export class SkuRecommendationResultsDialog {
|
|||||||
private _disposables: vscode.Disposable[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
public title?: string;
|
public title?: string;
|
||||||
public targetName?: string;
|
public targetName?: string;
|
||||||
|
private _saveButton!: azdata.window.Button;
|
||||||
|
|
||||||
public targetRecommendations?: mssql.SkuRecommendationResultItem[];
|
public targetRecommendations?: mssql.SkuRecommendationResultItem[];
|
||||||
public instanceRequirements?: mssql.SqlInstanceRequirements;
|
public instanceRequirements?: mssql.SqlInstanceRequirements;
|
||||||
@@ -491,6 +495,48 @@ export class SkuRecommendationResultsDialog {
|
|||||||
// this.dialog.cancelButton.label = SkuRecommendationResultsDialog.CreateTargetButtonText;
|
// this.dialog.cancelButton.label = SkuRecommendationResultsDialog.CreateTargetButtonText;
|
||||||
// this._disposables.push(this.dialog.cancelButton.onClick(async () => console.log(SkuRecommendationResultsDialog.CreateTargetButtonText)));
|
// this._disposables.push(this.dialog.cancelButton.onClick(async () => console.log(SkuRecommendationResultsDialog.CreateTargetButtonText)));
|
||||||
|
|
||||||
|
this._saveButton = azdata.window.createButton(
|
||||||
|
constants.SAVE_RECOMMENDATION_REPORT,
|
||||||
|
'left');
|
||||||
|
this._disposables.push(
|
||||||
|
this._saveButton.onClick(async () => {
|
||||||
|
const folder = await utils.promptUserForFolder();
|
||||||
|
|
||||||
|
if (this.model._skuRecommendationReportFilePaths) {
|
||||||
|
|
||||||
|
let sourceFilePath: string | undefined;
|
||||||
|
let destinationFilePath: string | undefined;
|
||||||
|
|
||||||
|
switch (this._targetType) {
|
||||||
|
case MigrationTargetType.SQLMI:
|
||||||
|
sourceFilePath = this.model._skuRecommendationReportFilePaths.find(filePath => filePath.includes('SkuRecommendationReport-AzureSqlManagedInstance'));
|
||||||
|
destinationFilePath = path.join(folder, 'SkuRecommendationReport-AzureSqlManagedInstance.html');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MigrationTargetType.SQLVM:
|
||||||
|
sourceFilePath = this.model._skuRecommendationReportFilePaths.find(filePath => filePath.includes('SkuRecommendationReport-AzureSqlVirtualMachine'));
|
||||||
|
destinationFilePath = path.join(folder, 'SkuRecommendationReport-AzureSqlVirtualMachine.html');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MigrationTargetType.SQLDB:
|
||||||
|
sourceFilePath = this.model._skuRecommendationReportFilePaths.find(filePath => filePath.includes('SkuRecommendationReport-AzureSqlDatabase'));
|
||||||
|
destinationFilePath = path.join(folder, 'SkuRecommendationReport-AzureSqlDatabase.html');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.copyFile(sourceFilePath!, destinationFilePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
} else {
|
||||||
|
void vscode.window.showInformationMessage(constants.SAVE_RECOMMENDATION_REPORT_SUCCESS(destinationFilePath!));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('recommendation report not found');
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this.dialog.customButtons = [this._saveButton];
|
||||||
|
|
||||||
const dialogSetupPromises: Thenable<void>[] = [];
|
const dialogSetupPromises: Thenable<void>[] = [];
|
||||||
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
||||||
azdata.window.openDialog(this.dialog);
|
azdata.window.openDialog(this.dialog);
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
public _assessedDatabaseList!: string[];
|
public _assessedDatabaseList!: string[];
|
||||||
public _runAssessments: boolean = true;
|
public _runAssessments: boolean = true;
|
||||||
private _assessmentApiResponse!: mssql.AssessmentResult;
|
private _assessmentApiResponse!: mssql.AssessmentResult;
|
||||||
|
public _assessmentReportFilePath: string;
|
||||||
public mementoString: string;
|
public mementoString: string;
|
||||||
|
|
||||||
public _databasesForMigration: string[] = [];
|
public _databasesForMigration: string[] = [];
|
||||||
@@ -210,6 +211,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
public _skuRecommendationResults!: SkuRecommendation;
|
public _skuRecommendationResults!: SkuRecommendation;
|
||||||
public _skuRecommendationPerformanceDataSource!: PerformanceDataSourceOptions;
|
public _skuRecommendationPerformanceDataSource!: PerformanceDataSourceOptions;
|
||||||
private _skuRecommendationApiResponse!: mssql.SkuRecommendationResult;
|
private _skuRecommendationApiResponse!: mssql.SkuRecommendationResult;
|
||||||
|
public _skuRecommendationReportFilePaths: string[];
|
||||||
public _skuRecommendationPerformanceLocation!: string;
|
public _skuRecommendationPerformanceLocation!: string;
|
||||||
private _skuRecommendationRecommendedDatabaseList!: string[];
|
private _skuRecommendationRecommendedDatabaseList!: string[];
|
||||||
private _startPerfDataCollectionApiResponse!: mssql.StartPerfDataCollectionResult;
|
private _startPerfDataCollectionApiResponse!: mssql.StartPerfDataCollectionResult;
|
||||||
@@ -263,6 +265,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
this._databaseBackup.networkShares = [];
|
this._databaseBackup.networkShares = [];
|
||||||
this._databaseBackup.blobs = [];
|
this._databaseBackup.blobs = [];
|
||||||
this._targetDatabaseNames = [];
|
this._targetDatabaseNames = [];
|
||||||
|
this._assessmentReportFilePath = '';
|
||||||
|
this._skuRecommendationReportFilePaths = [];
|
||||||
this.mementoString = 'sqlMigration.assessmentResults';
|
this.mementoString = 'sqlMigration.assessmentResults';
|
||||||
|
|
||||||
this._skuScalingFactor = 100;
|
this._skuScalingFactor = 100;
|
||||||
@@ -325,6 +329,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
}) ?? [],
|
}) ?? [],
|
||||||
errors: this._assessmentApiResponse?.errors ?? []
|
errors: this._assessmentApiResponse?.errors ?? []
|
||||||
};
|
};
|
||||||
|
this._assessmentReportFilePath = response.assessmentReportPath;
|
||||||
} else {
|
} else {
|
||||||
this._assessmentResults = {
|
this._assessmentResults = {
|
||||||
issues: [],
|
issues: [],
|
||||||
@@ -394,16 +399,19 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
sqlDbRecommendationResults: response?.sqlDbRecommendationResults ?? [],
|
sqlDbRecommendationResults: response?.sqlDbRecommendationResults ?? [],
|
||||||
sqlMiRecommendationResults: response?.sqlMiRecommendationResults ?? [],
|
sqlMiRecommendationResults: response?.sqlMiRecommendationResults ?? [],
|
||||||
sqlVmRecommendationResults: response?.sqlVmRecommendationResults ?? [],
|
sqlVmRecommendationResults: response?.sqlVmRecommendationResults ?? [],
|
||||||
instanceRequirements: response?.instanceRequirements
|
instanceRequirements: response?.instanceRequirements,
|
||||||
|
skuRecommendationReportPaths: response?.skuRecommendationReportPaths
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
this._skuRecommendationReportFilePaths = response.skuRecommendationReportPaths;
|
||||||
} else {
|
} else {
|
||||||
this._skuRecommendationResults = {
|
this._skuRecommendationResults = {
|
||||||
recommendations: {
|
recommendations: {
|
||||||
sqlDbRecommendationResults: [],
|
sqlDbRecommendationResults: [],
|
||||||
sqlMiRecommendationResults: [],
|
sqlMiRecommendationResults: [],
|
||||||
sqlVmRecommendationResults: [],
|
sqlVmRecommendationResults: [],
|
||||||
instanceRequirements: response?.instanceRequirements
|
instanceRequirements: response?.instanceRequirements,
|
||||||
|
skuRecommendationReportPaths: response?.skuRecommendationReportPaths
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -416,7 +424,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
sqlDbRecommendationResults: this._skuRecommendationApiResponse?.sqlDbRecommendationResults ?? [],
|
sqlDbRecommendationResults: this._skuRecommendationApiResponse?.sqlDbRecommendationResults ?? [],
|
||||||
sqlMiRecommendationResults: this._skuRecommendationApiResponse?.sqlMiRecommendationResults ?? [],
|
sqlMiRecommendationResults: this._skuRecommendationApiResponse?.sqlMiRecommendationResults ?? [],
|
||||||
sqlVmRecommendationResults: this._skuRecommendationApiResponse?.sqlVmRecommendationResults ?? [],
|
sqlVmRecommendationResults: this._skuRecommendationApiResponse?.sqlVmRecommendationResults ?? [],
|
||||||
instanceRequirements: this._skuRecommendationApiResponse?.instanceRequirements
|
instanceRequirements: this._skuRecommendationApiResponse?.instanceRequirements,
|
||||||
|
skuRecommendationReportPaths: this._skuRecommendationApiResponse?.skuRecommendationReportPaths
|
||||||
},
|
},
|
||||||
recommendationError: error
|
recommendationError: error
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user