Files
azuredatastudio/extensions/sql-assessment/src/engine.ts
Nemanja Milovančević 5e68ff1dfe Make mssql extension a module (#18804)
* Rebase from main branch

* import from module

* Add mssql module ref

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
2022-03-24 11:09:55 -07:00

186 lines
7.1 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as mssql from 'mssql';
import * as azdata from 'azdata';
import { createHistoryFileName, readHistoryFileNames, getAssessmentDate, TargetWithChildren } from './utils';
import { promises as fs } from 'fs';
import { TelemetryReporter, SqlAssessmentTelemetryView, SqlTelemetryActions } from './telemetry';
export enum AssessmentType {
AvailableRules = 1,
InvokeAssessment = 2
}
export type OnResultCallback = (result: azdata.SqlAssessmentResult, assessmentType: AssessmentType, append: boolean) => void;
export interface SqlAssessmentRecord {
result: azdata.SqlAssessmentResult;
dateUpdated: number;
}
interface SqlAssessmentResultInfo extends SqlAssessmentRecord {
connectionInfo: azdata.connection.ConnectionProfile
}
export class AssessmentEngine {
private sqlAssessment!: mssql.ISqlAssessmentService;
private connectionUri: string = '';
private connectionProfile!: azdata.connection.ConnectionProfile;
private lastInvokedResults!: SqlAssessmentResultInfo;
private historicalRecords!: SqlAssessmentRecord[] | undefined;
constructor(service: mssql.ISqlAssessmentService) {
this.sqlAssessment = service;
}
public get isServerConnection(): boolean {
return !this.connectionProfile.databaseName || this.connectionProfile.databaseName === 'master';
}
public get databaseName(): string {
return this.connectionProfile.databaseName;
}
public get recentResult(): SqlAssessmentResultInfo {
return this.lastInvokedResults;
}
public get targetName(): string {
return this.isServerConnection ? this.connectionProfile.serverName : this.connectionProfile.databaseName;
}
public async initialize(connectionId: string) {
this.connectionUri = await azdata.connection.getUriForConnection(connectionId);
this.connectionProfile = await azdata.connection.getCurrentConnection();
this.historicalRecords = undefined;
}
public async performAssessment(asmtType: AssessmentType, onResult: OnResultCallback): Promise<void> {
if (this.isServerConnection) {
await this.performServerAssessment(asmtType, onResult);
} else {
if (asmtType === AssessmentType.AvailableRules) {
TelemetryReporter.sendActionEvent(SqlAssessmentTelemetryView, SqlTelemetryActions.GetDatabaseAssessmentRules);
await onResult(await this.sqlAssessment.getAssessmentItems(this.connectionUri, azdata.sqlAssessment.SqlAssessmentTargetType.Database), asmtType, false);
} else {
TelemetryReporter.sendActionEvent(SqlAssessmentTelemetryView, SqlTelemetryActions.InvokeDatabaseAssessment);
const result = await this.sqlAssessment.assessmentInvoke(this.connectionUri, azdata.sqlAssessment.SqlAssessmentTargetType.Database);
this.lastInvokedResults = {
connectionInfo: this.connectionProfile,
dateUpdated: Date.now(),
result: result
};
await onResult(result, asmtType, false);
this.saveAssessment(this.databaseName, result);
}
}
if (asmtType === AssessmentType.InvokeAssessment && this.historicalRecords !== undefined) {
this.historicalRecords.push({
result: this.lastInvokedResults.result,
dateUpdated: this.lastInvokedResults.dateUpdated
});
}
}
public generateAssessmentScript(): Promise<azdata.ResultStatus> {
TelemetryReporter.sendActionEvent(SqlAssessmentTelemetryView, SqlTelemetryActions.ExportAssessmentResults);
return this.sqlAssessment.generateAssessmentScript(this.lastInvokedResults.result.items, '', '', azdata.TaskExecutionMode.script);
}
public async readHistory(): Promise<SqlAssessmentRecord[]> {
if (this.historicalRecords === undefined) {
await this.loadHistory();
}
return this.historicalRecords ?? [];
}
private async loadHistory(): Promise<void> {
this.historicalRecords = [];
const files: TargetWithChildren[] = await readHistoryFileNames(this.targetName);
for (let nFileName = 0; nFileName < files.length; nFileName++) {
const file: TargetWithChildren = files[nFileName];
const content: string = await fs.readFile(file.target, 'utf8');
const result: azdata.SqlAssessmentResult = JSON.parse(content);
if (this.isServerConnection) {
for (let nChild = 0; nChild < file.children.length; nChild++) {
const childResult: azdata.SqlAssessmentResult = JSON.parse(await fs.readFile(file.children[nChild], 'utf8'));
result.items.push(...childResult.items);
}
}
const date = getAssessmentDate(file.target);
this.historicalRecords.push({
dateUpdated: date,
result: result
});
}
}
private async performServerAssessment(asmtType: AssessmentType, onResult: OnResultCallback): Promise<void> {
let databaseListRequest = azdata.connection.listDatabases(this.connectionProfile.connectionId);
let assessmentResult: azdata.SqlAssessmentResult;
if (asmtType === AssessmentType.InvokeAssessment) {
TelemetryReporter.sendActionEvent(SqlAssessmentTelemetryView, SqlTelemetryActions.InvokeServerAssessment);
assessmentResult = await this.sqlAssessment.assessmentInvoke(this.connectionUri, azdata.sqlAssessment.SqlAssessmentTargetType.Server);
this.lastInvokedResults = {
connectionInfo: this.connectionProfile,
dateUpdated: Date.now(),
result: assessmentResult
};
this.saveAssessment(this.connectionProfile.serverName, assessmentResult);
} else {
TelemetryReporter.sendActionEvent(SqlAssessmentTelemetryView, SqlTelemetryActions.GetServerAssessmentRules);
assessmentResult = await this.sqlAssessment.getAssessmentItems(this.connectionUri, azdata.sqlAssessment.SqlAssessmentTargetType.Server);
}
await onResult(assessmentResult, asmtType, false);
let connectionProvider = azdata.dataprotocol.getProvider<azdata.ConnectionProvider>(
this.connectionProfile.providerId, azdata.DataProviderType.ConnectionProvider);
const dbList = await databaseListRequest;
for (let nDbName = 0; nDbName < dbList.length; nDbName++) {
const db = dbList[nDbName];
if (await connectionProvider.changeDatabase(this.connectionUri, db)) {
let assessmentResult = asmtType === AssessmentType.InvokeAssessment
? await this.sqlAssessment.assessmentInvoke(this.connectionUri, azdata.sqlAssessment.SqlAssessmentTargetType.Database)
: await this.sqlAssessment.getAssessmentItems(this.connectionUri, azdata.sqlAssessment.SqlAssessmentTargetType.Database);
if (assessmentResult?.items) {
if (asmtType === AssessmentType.InvokeAssessment) {
this.lastInvokedResults.result.items.push(...assessmentResult?.items);
this.saveAssessment(db, assessmentResult);
}
await onResult(assessmentResult, asmtType, true);
}
}
}
}
private async saveAssessment(target: string, assessment: azdata.SqlAssessmentResult): Promise<void> {
try {
const fileName = await createHistoryFileName(target, this.lastInvokedResults.dateUpdated);
return fs.writeFile(fileName, JSON.stringify(assessment));
}
catch (err) {
console.error(`error saving sql assessment history file: ${err}`);
}
}
}