mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 01:25: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:
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window, Account, accounts, CategoryValue, DropDownComponent, IconPath } from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||
import { MigrationStatus, ProvisioningState } from '../models/migrationLocalStorage';
|
||||
import * as crypto from 'crypto';
|
||||
@@ -865,3 +866,21 @@ export async function getBlobLastBackupFileNamesValues(lastFileNames: azureResou
|
||||
}
|
||||
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 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 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
|
||||
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 { SqlMigrationImpactedObjectInfo } from 'mssql';
|
||||
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 = {
|
||||
description: string,
|
||||
@@ -24,6 +28,8 @@ export class AssessmentResultsDialog {
|
||||
private _isOpen: boolean = false;
|
||||
private dialog: azdata.window.Dialog | undefined;
|
||||
private _model: MigrationStateModel;
|
||||
private _saveButton!: azdata.window.Button;
|
||||
private static readonly _assessmentReportName: string = 'SqlAssessmentReport.json';
|
||||
|
||||
// Dialog Name for Telemetry
|
||||
public dialogName: string | undefined;
|
||||
@@ -71,6 +77,27 @@ export class AssessmentResultsDialog {
|
||||
this.dialog.cancelButton.label = AssessmentResultsDialog.CancelButtonText;
|
||||
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>[] = [];
|
||||
|
||||
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
||||
|
||||
@@ -197,7 +197,7 @@ export class GetAzureRecommendationDialog {
|
||||
}
|
||||
}).component();
|
||||
this._disposables.push(browseButton.onDidClick(async (e) => {
|
||||
let folder = await this.handleBrowse();
|
||||
let folder = await utils.promptUserForFolder();
|
||||
this._collectDataFolderInput.value = folder;
|
||||
}));
|
||||
|
||||
@@ -259,7 +259,7 @@ export class GetAzureRecommendationDialog {
|
||||
}
|
||||
}).component();
|
||||
this._disposables.push(openButton.onDidClick(async (e) => {
|
||||
let folder = await this.handleBrowse();
|
||||
let folder = await utils.promptUserForFolder();
|
||||
this._openExistingFolderInput.value = folder;
|
||||
}));
|
||||
|
||||
@@ -380,23 +380,4 @@ export class GetAzureRecommendationDialog {
|
||||
public get isOpen(): boolean {
|
||||
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 styles from '../../constants/styles';
|
||||
import * as mssql from 'mssql';
|
||||
import * as utils from '../../api/utils';
|
||||
import * as fs from 'fs';
|
||||
import path = require('path');
|
||||
|
||||
export class SkuRecommendationResultsDialog {
|
||||
|
||||
@@ -23,6 +26,7 @@ export class SkuRecommendationResultsDialog {
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
public title?: string;
|
||||
public targetName?: string;
|
||||
private _saveButton!: azdata.window.Button;
|
||||
|
||||
public targetRecommendations?: mssql.SkuRecommendationResultItem[];
|
||||
public instanceRequirements?: mssql.SqlInstanceRequirements;
|
||||
@@ -491,6 +495,48 @@ export class SkuRecommendationResultsDialog {
|
||||
// this.dialog.cancelButton.label = 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>[] = [];
|
||||
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
||||
azdata.window.openDialog(this.dialog);
|
||||
|
||||
@@ -199,6 +199,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
public _assessedDatabaseList!: string[];
|
||||
public _runAssessments: boolean = true;
|
||||
private _assessmentApiResponse!: mssql.AssessmentResult;
|
||||
public _assessmentReportFilePath: string;
|
||||
public mementoString: string;
|
||||
|
||||
public _databasesForMigration: string[] = [];
|
||||
@@ -210,6 +211,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
public _skuRecommendationResults!: SkuRecommendation;
|
||||
public _skuRecommendationPerformanceDataSource!: PerformanceDataSourceOptions;
|
||||
private _skuRecommendationApiResponse!: mssql.SkuRecommendationResult;
|
||||
public _skuRecommendationReportFilePaths: string[];
|
||||
public _skuRecommendationPerformanceLocation!: string;
|
||||
private _skuRecommendationRecommendedDatabaseList!: string[];
|
||||
private _startPerfDataCollectionApiResponse!: mssql.StartPerfDataCollectionResult;
|
||||
@@ -263,6 +265,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
this._databaseBackup.networkShares = [];
|
||||
this._databaseBackup.blobs = [];
|
||||
this._targetDatabaseNames = [];
|
||||
this._assessmentReportFilePath = '';
|
||||
this._skuRecommendationReportFilePaths = [];
|
||||
this.mementoString = 'sqlMigration.assessmentResults';
|
||||
|
||||
this._skuScalingFactor = 100;
|
||||
@@ -325,6 +329,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
}) ?? [],
|
||||
errors: this._assessmentApiResponse?.errors ?? []
|
||||
};
|
||||
this._assessmentReportFilePath = response.assessmentReportPath;
|
||||
} else {
|
||||
this._assessmentResults = {
|
||||
issues: [],
|
||||
@@ -394,16 +399,19 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
sqlDbRecommendationResults: response?.sqlDbRecommendationResults ?? [],
|
||||
sqlMiRecommendationResults: response?.sqlMiRecommendationResults ?? [],
|
||||
sqlVmRecommendationResults: response?.sqlVmRecommendationResults ?? [],
|
||||
instanceRequirements: response?.instanceRequirements
|
||||
instanceRequirements: response?.instanceRequirements,
|
||||
skuRecommendationReportPaths: response?.skuRecommendationReportPaths
|
||||
},
|
||||
};
|
||||
this._skuRecommendationReportFilePaths = response.skuRecommendationReportPaths;
|
||||
} else {
|
||||
this._skuRecommendationResults = {
|
||||
recommendations: {
|
||||
sqlDbRecommendationResults: [],
|
||||
sqlMiRecommendationResults: [],
|
||||
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 ?? [],
|
||||
sqlMiRecommendationResults: this._skuRecommendationApiResponse?.sqlMiRecommendationResults ?? [],
|
||||
sqlVmRecommendationResults: this._skuRecommendationApiResponse?.sqlVmRecommendationResults ?? [],
|
||||
instanceRequirements: this._skuRecommendationApiResponse?.instanceRequirements
|
||||
instanceRequirements: this._skuRecommendationApiResponse?.instanceRequirements,
|
||||
skuRecommendationReportPaths: this._skuRecommendationApiResponse?.skuRecommendationReportPaths
|
||||
},
|
||||
recommendationError: error
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user