Assessment core extension (#10154)
@@ -208,7 +208,8 @@ const externalExtensions = [
|
||||
'query-history',
|
||||
'liveshare',
|
||||
'sql-database-projects',
|
||||
'machine-learning'
|
||||
'machine-learning',
|
||||
'sql-assessment'
|
||||
];
|
||||
// extensions that require a rebuild since they have native parts
|
||||
const rebuildExtensions = [
|
||||
|
||||
@@ -243,7 +243,8 @@ const externalExtensions = [
|
||||
'query-history',
|
||||
'liveshare',
|
||||
'sql-database-projects',
|
||||
'machine-learning'
|
||||
'machine-learning',
|
||||
'sql-assessment'
|
||||
];
|
||||
|
||||
// extensions that require a rebuild since they have native parts
|
||||
|
||||
@@ -678,26 +678,26 @@ export namespace SchemaCompareCancellationRequest {
|
||||
|
||||
// ------------------------------- <Schema Compare> -----------------------------
|
||||
|
||||
// ------------------------------- <Sql Assessment> -----------------------------
|
||||
/// ------------------------------- <Sql Assessment> -----------------------------
|
||||
|
||||
export interface SqlAssessmentParams {
|
||||
ownerUri: string;
|
||||
targetType: mssql.SqlAssessmentTargetType
|
||||
targetType: azdata.sqlAssessment.SqlAssessmentTargetType
|
||||
}
|
||||
|
||||
export interface GenerateSqlAssessmentScriptParams {
|
||||
items: mssql.SqlAssessmentResultItem[];
|
||||
items: azdata.SqlAssessmentResultItem[];
|
||||
taskExecutionMode: azdata.TaskExecutionMode;
|
||||
targetServerName: string;
|
||||
targetDatabaseName: string;
|
||||
}
|
||||
|
||||
export namespace SqlAssessmentInvokeRequest {
|
||||
export const type = new RequestType<SqlAssessmentParams, mssql.SqlAssessmentResult, void, void>('assessment/invoke');
|
||||
export const type = new RequestType<SqlAssessmentParams, azdata.SqlAssessmentResult, void, void>('assessment/invoke');
|
||||
}
|
||||
|
||||
export namespace GetSqlAssessmentItemsRequest {
|
||||
export const type = new RequestType<SqlAssessmentParams, mssql.SqlAssessmentResult, void, void>('assessment/getAssessmentItems');
|
||||
export const type = new RequestType<SqlAssessmentParams, azdata.SqlAssessmentResult, void, void>('assessment/getAssessmentItems');
|
||||
}
|
||||
|
||||
export namespace GenerateSqlAssessmentScriptRequest {
|
||||
@@ -706,7 +706,6 @@ export namespace GenerateSqlAssessmentScriptRequest {
|
||||
|
||||
// ------------------------------- <Sql Assessment> -----------------------------
|
||||
|
||||
|
||||
// ------------------------------- <Serialization> -----------------------------
|
||||
export namespace SerializeDataStartRequest {
|
||||
export const type = new RequestType<azdata.SerializeDataStartRequestParams, azdata.SerializeDataResult, void, void>('serialize/start');
|
||||
|
||||
@@ -746,7 +746,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Job management methods
|
||||
return azdata.dataprotocol.registerAgentServicesProvider({
|
||||
providerId: client.providerId,
|
||||
getJobs,
|
||||
@@ -852,3 +852,72 @@ export class SerializationFeature extends SqlOpsFeature<undefined> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class SqlAssessmentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
contracts.SqlAssessmentInvokeRequest.type,
|
||||
contracts.GetSqlAssessmentItemsRequest.type
|
||||
];
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, SqlAssessmentServicesFeature.messagesTypes);
|
||||
}
|
||||
|
||||
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
}
|
||||
|
||||
public initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let assessmentInvoke = async (ownerUri: string, targetType: azdata.sqlAssessment.SqlAssessmentTargetType): Promise<azdata.SqlAssessmentResult> => {
|
||||
let params: contracts.SqlAssessmentParams = { ownerUri: ownerUri, targetType: targetType };
|
||||
try {
|
||||
return client.sendRequest(contracts.SqlAssessmentInvokeRequest.type, params);
|
||||
}
|
||||
catch (e) {
|
||||
client.logFailedRequest(contracts.SqlAssessmentInvokeRequest.type, e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
let getAssessmentItems = async (ownerUri: string, targetType: azdata.sqlAssessment.SqlAssessmentTargetType): Promise<azdata.SqlAssessmentResult> => {
|
||||
let params: contracts.SqlAssessmentParams = { ownerUri: ownerUri, targetType: targetType };
|
||||
try {
|
||||
return client.sendRequest(contracts.GetSqlAssessmentItemsRequest.type, params);
|
||||
}
|
||||
catch (e) {
|
||||
client.logFailedRequest(contracts.GetSqlAssessmentItemsRequest.type, e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
let generateAssessmentScript = async (items: azdata.SqlAssessmentResultItem[]): Promise<azdata.ResultStatus> => {
|
||||
let params: contracts.GenerateSqlAssessmentScriptParams = { items: items, taskExecutionMode: azdata.TaskExecutionMode.script, targetServerName: '', targetDatabaseName: '' };
|
||||
try {
|
||||
return client.sendRequest(contracts.GenerateSqlAssessmentScriptRequest.type, params);
|
||||
}
|
||||
catch (e) {
|
||||
client.logFailedRequest(contracts.GenerateSqlAssessmentScriptRequest.type, e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return azdata.dataprotocol.registerSqlAssessmentServicesProvider({
|
||||
providerId: client.providerId,
|
||||
assessmentInvoke,
|
||||
getAssessmentItems,
|
||||
generateAssessmentScript
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
36
extensions/mssql/src/mssql.d.ts
vendored
@@ -483,40 +483,10 @@ export interface ListRegisteredServersResult {
|
||||
|
||||
// SqlAssessment interfaces -----------------------------------------------------------------------
|
||||
|
||||
export const enum SqlAssessmentTargetType {
|
||||
Server = 1,
|
||||
Database = 2
|
||||
}
|
||||
|
||||
export const enum SqlAssessmentResultItemKind {
|
||||
RealResult = 0,
|
||||
Warning = 1,
|
||||
Error = 2
|
||||
}
|
||||
|
||||
export interface SqlAssessmentResultItem {
|
||||
rulesetVersion: string;
|
||||
rulesetName: string;
|
||||
targetType: SqlAssessmentTargetType;
|
||||
targetName: string;
|
||||
checkId: string;
|
||||
tags: string[];
|
||||
displayName: string;
|
||||
description: string;
|
||||
message: string;
|
||||
helpLink: string;
|
||||
level: string;
|
||||
timestamp: string;
|
||||
kind: SqlAssessmentResultItemKind;
|
||||
}
|
||||
|
||||
export interface SqlAssessmentResult extends azdata.ResultStatus {
|
||||
items: SqlAssessmentResultItem[];
|
||||
apiVersion: string;
|
||||
}
|
||||
|
||||
export interface ISqlAssessmentService {
|
||||
assessmentInvoke(ownerUri: string, targetType: SqlAssessmentTargetType): Promise<SqlAssessmentResult>;
|
||||
getAssessmentItems(ownerUri: string, targetType: SqlAssessmentTargetType): Promise<SqlAssessmentResult>;
|
||||
generateAssessmentScript(items: SqlAssessmentResultItem[], targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<azdata.ResultStatus>;
|
||||
assessmentInvoke(ownerUri: string, targetType: azdata.sqlAssessment.SqlAssessmentTargetType): Promise<azdata.SqlAssessmentResult>;
|
||||
getAssessmentItems(ownerUri: string, targetType: azdata.sqlAssessment.SqlAssessmentTargetType): Promise<azdata.SqlAssessmentResult>;
|
||||
generateAssessmentScript(items: azdata.SqlAssessmentResultItem[], targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<azdata.ResultStatus>;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export class SqlAssessmentService implements mssql.ISqlAssessmentService {
|
||||
private constructor(context: AppContext, protected readonly client: SqlOpsDataClient) {
|
||||
context.registerService(constants.SqlAssessmentService, this);
|
||||
}
|
||||
async assessmentInvoke(ownerUri: string, targetType: mssql.SqlAssessmentTargetType): Promise<mssql.SqlAssessmentResult | undefined> {
|
||||
async assessmentInvoke(ownerUri: string, targetType: azdata.sqlAssessment.SqlAssessmentTargetType): Promise<azdata.SqlAssessmentResult | undefined> {
|
||||
let params: contracts.SqlAssessmentParams = { ownerUri: ownerUri, targetType: targetType };
|
||||
try {
|
||||
return this.client.sendRequest(contracts.SqlAssessmentInvokeRequest.type, params);
|
||||
@@ -41,7 +41,7 @@ export class SqlAssessmentService implements mssql.ISqlAssessmentService {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
async getAssessmentItems(ownerUri: string, targetType: mssql.SqlAssessmentTargetType): Promise<mssql.SqlAssessmentResult | undefined> {
|
||||
async getAssessmentItems(ownerUri: string, targetType: azdata.sqlAssessment.SqlAssessmentTargetType): Promise<azdata.SqlAssessmentResult | undefined> {
|
||||
let params: contracts.SqlAssessmentParams = { ownerUri: ownerUri, targetType: targetType };
|
||||
try {
|
||||
return this.client.sendRequest(contracts.GetSqlAssessmentItemsRequest.type, params);
|
||||
@@ -52,7 +52,7 @@ export class SqlAssessmentService implements mssql.ISqlAssessmentService {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
async generateAssessmentScript(items: mssql.SqlAssessmentResultItem[], targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<azdata.ResultStatus | undefined> {
|
||||
async generateAssessmentScript(items: azdata.SqlAssessmentResultItem[], targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<azdata.ResultStatus | undefined> {
|
||||
let params: contracts.GenerateSqlAssessmentScriptParams = { items: items, targetServerName: targetServerName, targetDatabaseName: targetDatabaseName, taskExecutionMode: taskExecutionMode };
|
||||
try {
|
||||
return this.client.sendRequest(contracts.GenerateSqlAssessmentScriptRequest.type, params);
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as path from 'path';
|
||||
import { getCommonLaunchArgsAndCleanupOldLogFiles } from './utils';
|
||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
||||
import { TelemetryFeature, AgentServicesFeature, SerializationFeature, AccountFeature } from './features';
|
||||
import { TelemetryFeature, AgentServicesFeature, SerializationFeature, AccountFeature, SqlAssessmentServicesFeature } from './features';
|
||||
import { CredentialStore } from './credentialstore/credentialstore';
|
||||
import { AzureResourceProvider } from './resourceProvider/resourceProvider';
|
||||
import { SchemaCompareService } from './schemaCompare/schemaCompareService';
|
||||
@@ -155,6 +155,7 @@ function getClientOptions(context: AppContext): ClientOptions {
|
||||
AccountFeature,
|
||||
AgentServicesFeature,
|
||||
SerializationFeature,
|
||||
SqlAssessmentServicesFeature,
|
||||
SchemaCompareService.asFeature(context),
|
||||
LanguageExtensionService.asFeature(context),
|
||||
DacFxService.asFeature(context),
|
||||
|
||||
BIN
extensions/sql-assessment/images/extension.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
43
extensions/sql-assessment/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "sql-assessment",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"version": "0.1.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||
"icon": "images/extension.png",
|
||||
"aiKey": "AIF-37eefaf0-8022-4671-a3fb-64752724682e",
|
||||
"engines": {
|
||||
"vscode": "^1.25.0",
|
||||
"azdata": ">=1.18.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"onDashboardOpen"
|
||||
],
|
||||
"main": "./out/main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/azuredatastudio.git"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
"Microsoft.mssql"
|
||||
],
|
||||
"contributes": {
|
||||
"dashboard.tabs": [{
|
||||
"id": "data-management-asmt",
|
||||
"description": "%dashboard.tabName%",
|
||||
"provider": "MSSQL",
|
||||
"title": "%displayName%",
|
||||
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud && mssql:engineedition != 11",
|
||||
"container": {
|
||||
"controlhost-container": {
|
||||
"type": "assessment"
|
||||
}
|
||||
}
|
||||
}]
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.1"
|
||||
}
|
||||
}
|
||||
5
extensions/sql-assessment/package.nls.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"displayName": "SQL Server Assessment",
|
||||
"description": "SQL Server Assessment for Azure Data Studio (Preview) provides a mechanism to evaluate the configuration of SQL Server for best practices.",
|
||||
"dashboard.tabName": "SQL Assessment"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>notebook_inverse</title><path class="cls-1" d="M15.46,2V15H.46V2h2V1h3a4.19,4.19,0,0,1,1.32.21A3.87,3.87,0,0,1,8,1.84a3.87,3.87,0,0,1,1.18-.63A4.19,4.19,0,0,1,10.46,1h3V2Zm-14,12h6.3a4.43,4.43,0,0,0-.51-.43,3.41,3.41,0,0,0-.54-.31,2.74,2.74,0,0,0-.59-.2A3.2,3.2,0,0,0,5.46,13h-3V3h-1Zm2-2h2a4.18,4.18,0,0,1,1,.13,4,4,0,0,1,1,.39V2.72a3,3,0,0,0-.94-.54A3.15,3.15,0,0,0,5.46,2h-2Zm11-9h-1V13h-3a3.2,3.2,0,0,0-.67.07,2.74,2.74,0,0,0-.59.2,3.41,3.41,0,0,0-.54.31,4.43,4.43,0,0,0-.51.43h6.3Zm-4-1a3.15,3.15,0,0,0-1.06.18,3,3,0,0,0-.94.54v9.8a4,4,0,0,1,1-.39,4.18,4.18,0,0,1,1-.13h2V2Z"/></svg>
|
||||
|
After Width: | Height: | Size: 734 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#0095d7;}</style></defs><title>open_notebook_inverse</title><path class="cls-1" d="M12.55,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9.18a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.29,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.93,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.67,3.2H2v.9H.15V15.85H13.72V5.48ZM2.86,4.1H4.67a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.86ZM1,15V5H2v9H4.67a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39ZM12.8,15H7.11a2.7,2.7,0,0,1,.47-.39A2.83,2.83,0,0,1,8,14.28a3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9.18,14h2.73V5h.89Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><path class="cls-2" d="M13.19,3.57h0v0Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><polygon class="cls-2" points="14.21 1.65 14.19 1.65 14.19 1.63 14.21 1.65"/><path class="cls-2" d="M15.91,2.1,14.2,3.81l-.38.38-.62-.61v0l1-1H12.79a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47A3.41,3.41,0,0,0,9.5,6.11H8.6a4.68,4.68,0,0,1,.16-1.19A4.74,4.74,0,0,1,9,4.26a2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16,2.79,2.79,0,0,1,.34-.18l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.82,0Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
extensions/sql-assessment/resources/light/notebook.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>notebook</title><path d="M15.5,2V15H.5V2h2V1h3a4.19,4.19,0,0,1,1.32.21A3.87,3.87,0,0,1,8,1.84a3.87,3.87,0,0,1,1.18-.63A4.19,4.19,0,0,1,10.5,1h3V2ZM1.5,14H7.8a4.43,4.43,0,0,0-.51-.43,3.41,3.41,0,0,0-.54-.31,2.74,2.74,0,0,0-.59-.2A3.2,3.2,0,0,0,5.5,13h-3V3h-1Zm2-2h2a4.18,4.18,0,0,1,1,.13,4,4,0,0,1,1,.39V2.72a3,3,0,0,0-.94-.54A3.15,3.15,0,0,0,5.5,2h-2Zm11-9h-1V13h-3a3.2,3.2,0,0,0-.67.07,2.74,2.74,0,0,0-.59.2,3.41,3.41,0,0,0-.54.31A4.43,4.43,0,0,0,8.2,14h6.3Zm-4-1a3.15,3.15,0,0,0-1.06.18,3,3,0,0,0-.94.54v9.8a4,4,0,0,1,1-.39,4.18,4.18,0,0,1,1-.13h2V2Z"/></svg>
|
||||
|
After Width: | Height: | Size: 661 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#00539c;}</style></defs><title>open_notebook</title><path d="M12.4,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.14,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.78,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.52,3.2H1.81v.9H0V15.85H13.57V5.48ZM2.71,4.1H4.52a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.71ZM.9,15V5h.91v9H4.52a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39Zm11.75,0H7a2.7,2.7,0,0,1,.47-.39,2.83,2.83,0,0,1,.47-.29,3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9,14h2.73V5h.89Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><path class="cls-1" d="M13,3.57h0v0Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><polygon class="cls-1" points="14.06 1.65 14.04 1.65 14.04 1.63 14.06 1.65"/><path class="cls-1" d="M15.76,2.1,14,3.81l-.38.38L13,3.58v0l1-1H12.64a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47,3.41,3.41,0,0,0-.27,1.39h-.9a4.68,4.68,0,0,1,.16-1.19,4.74,4.74,0,0,1,.25-.66,2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16A2.79,2.79,0,0,1,11,2.08l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.67,0Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
13
extensions/sql-assessment/src/main.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(_context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate(): void {
|
||||
}
|
||||
9
extensions/sql-assessment/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/azdata.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
14
extensions/sql-assessment/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../shared.tsconfig.json",
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
"lib": [
|
||||
"es6", "es2015.promise"
|
||||
],
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
8
extensions/sql-assessment/yarn.lock
Normal file
@@ -0,0 +1,8 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
vscode-nls@^3.2.1:
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
|
||||
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
|
||||
6
src/sql/azdata.d.ts
vendored
@@ -1798,8 +1798,9 @@ declare module 'azdata' {
|
||||
deleteJobSchedule(ownerUri: string, scheduleInfo: AgentJobScheduleInfo): Thenable<ResultStatus>;
|
||||
|
||||
registerOnUpdated(handler: () => any): void;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
// DacFx interfaces -----------------------------------------------------------------------
|
||||
|
||||
// Security service interfaces ------------------------------------------------------------------------
|
||||
@@ -4102,7 +4103,8 @@ declare module 'azdata' {
|
||||
CapabilitiesProvider = 'CapabilitiesProvider',
|
||||
ObjectExplorerNodeProvider = 'ObjectExplorerNodeProvider',
|
||||
IconProvider = 'IconProvider',
|
||||
SerializationProvider = 'SerializationProvider'
|
||||
SerializationProvider = 'SerializationProvider',
|
||||
SqlAssessmentServicesProvider = 'SqlAssessmentServicesProvider'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
43
src/sql/azdata.proposed.d.ts
vendored
@@ -86,6 +86,7 @@ declare module 'azdata' {
|
||||
|
||||
export namespace dataprotocol {
|
||||
export function registerSerializationProvider(provider: SerializationProvider): vscode.Disposable;
|
||||
export function registerSqlAssessmentServicesProvider(provider: SqlAssessmentServicesProvider): vscode.Disposable;
|
||||
}
|
||||
|
||||
export interface HyperlinkComponent {
|
||||
@@ -399,5 +400,47 @@ declare module 'azdata' {
|
||||
export interface TaskInfo {
|
||||
targetLocation?: string;
|
||||
}
|
||||
|
||||
export namespace sqlAssessment {
|
||||
|
||||
export enum SqlAssessmentTargetType {
|
||||
Server = 1,
|
||||
Database = 2
|
||||
}
|
||||
|
||||
export enum SqlAssessmentResultItemKind {
|
||||
RealResult = 0,
|
||||
Warning = 1,
|
||||
Error = 2
|
||||
}
|
||||
}
|
||||
// Assessment interfaces
|
||||
|
||||
export interface SqlAssessmentResultItem {
|
||||
rulesetVersion: string;
|
||||
rulesetName: string;
|
||||
targetType: sqlAssessment.SqlAssessmentTargetType;
|
||||
targetName: string;
|
||||
checkId: string;
|
||||
tags: string[];
|
||||
displayName: string;
|
||||
description: string;
|
||||
message: string;
|
||||
helpLink: string;
|
||||
level: string;
|
||||
timestamp: string;
|
||||
kind: sqlAssessment.SqlAssessmentResultItemKind;
|
||||
}
|
||||
|
||||
export interface SqlAssessmentResult extends ResultStatus {
|
||||
items: SqlAssessmentResultItem[];
|
||||
apiVersion: string;
|
||||
}
|
||||
|
||||
export interface SqlAssessmentServicesProvider extends DataProvider {
|
||||
assessmentInvoke(ownerUri: string, targetType: sqlAssessment.SqlAssessmentTargetType): Promise<SqlAssessmentResult>;
|
||||
getAssessmentItems(ownerUri: string, targetType: sqlAssessment.SqlAssessmentTargetType): Promise<SqlAssessmentResult>;
|
||||
generateAssessmentScript(items: SqlAssessmentResultItem[]): Promise<ResultStatus>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
src/sql/platform/opener/common/openerServiceStub.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
|
||||
export class OpenerServiceStub implements IOpenerService {
|
||||
_serviceBrand: undefined;
|
||||
registerOpener() { return undefined; }
|
||||
registerValidator() { return undefined; }
|
||||
registerExternalUriResolver() { return undefined; }
|
||||
setExternalOpener() { return undefined; }
|
||||
async open(resource: URI | string, options?: any): Promise<boolean> { return Promise.resolve(true); }
|
||||
async resolveExternalUri(uri: any) { return undefined; }
|
||||
}
|
||||
@@ -64,10 +64,12 @@ export enum TelemetryView {
|
||||
Shell = 'Shell',
|
||||
ExtensionRecommendationDialog = 'ExtensionRecommendationDialog',
|
||||
ResultsPanel = 'ResultsPanel',
|
||||
Notebook = 'Notebook'
|
||||
Notebook = 'Notebook',
|
||||
SqlAssessment = 'SqlAssessment'
|
||||
}
|
||||
|
||||
export enum TelemetryAction {
|
||||
Click = 'Click',
|
||||
Open = 'Open'
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { serializableToMap } from 'sql/base/common/map';
|
||||
import { IAssessmentService } from 'sql/workbench/services/assessment/common/interfaces';
|
||||
|
||||
/**
|
||||
* Main thread class for handling data protocol management registration.
|
||||
@@ -53,7 +54,8 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
|
||||
@ITaskService private _taskService: ITaskService,
|
||||
@IProfilerService private _profilerService: IProfilerService,
|
||||
@ISerializationService private _serializationService: ISerializationService,
|
||||
@IFileBrowserService private _fileBrowserService: IFileBrowserService
|
||||
@IFileBrowserService private _fileBrowserService: IFileBrowserService,
|
||||
@IAssessmentService private _assessmentService: IAssessmentService
|
||||
) {
|
||||
super();
|
||||
if (extHostContext) {
|
||||
@@ -447,6 +449,23 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public $registerSqlAssessmentServicesProvider(providerId: string, handle: number): Promise<any> {
|
||||
const self = this;
|
||||
this._assessmentService.registerProvider(providerId, <azdata.SqlAssessmentServicesProvider>{
|
||||
providerId: providerId,
|
||||
assessmentInvoke(connectionUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult> {
|
||||
return self._proxy.$assessmentInvoke(handle, connectionUri, targetType);
|
||||
},
|
||||
getAssessmentItems(connectionUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult> {
|
||||
return self._proxy.$getAssessmentItems(handle, connectionUri, targetType);
|
||||
},
|
||||
generateAssessmentScript(items: azdata.SqlAssessmentResultItem[]): Thenable<azdata.ResultStatus> {
|
||||
return self._proxy.$generateAssessmentScript(handle, items);
|
||||
}
|
||||
});
|
||||
|
||||
return undefined;
|
||||
}
|
||||
public $registerCapabilitiesServiceProvider(providerId: string, handle: number): Promise<any> {
|
||||
const self = this;
|
||||
this._capabilitiesService.registerProvider(<azdata.CapabilitiesProvider>{
|
||||
|
||||
@@ -168,7 +168,11 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
||||
this._proxy.$registerAgentServicesProvider(provider.providerId, provider.handle);
|
||||
return rt;
|
||||
}
|
||||
|
||||
$registerSqlAssessmentServiceProvider(provider: azdata.SqlAssessmentServicesProvider): vscode.Disposable {
|
||||
let rt = this.registerProvider(provider, DataProviderType.SqlAssessmentServicesProvider);
|
||||
this._proxy.$registerSqlAssessmentServicesProvider(provider.providerId, provider.handle);
|
||||
return rt;
|
||||
}
|
||||
$registerCapabilitiesServiceProvider(provider: azdata.CapabilitiesProvider): vscode.Disposable {
|
||||
let rt = this.registerProvider(provider, DataProviderType.CapabilitiesProvider);
|
||||
this._proxy.$registerCapabilitiesServiceProvider(provider.providerId, provider.handle);
|
||||
@@ -839,4 +843,17 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
||||
public $continueSerialization(handle: number, requestParams: azdata.SerializeDataContinueRequestParams): Thenable<azdata.SerializeDataResult> {
|
||||
return this._resolveProvider<azdata.SerializationProvider>(handle).continueSerialization(requestParams);
|
||||
}
|
||||
|
||||
// Assessment methods
|
||||
public $assessmentInvoke(handle: number, ownerUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult> {
|
||||
return this._resolveProvider<azdata.SqlAssessmentServicesProvider>(handle).assessmentInvoke(ownerUri, targetType);
|
||||
}
|
||||
|
||||
public $getAssessmentItems(handle: number, ownerUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult> {
|
||||
return this._resolveProvider<azdata.SqlAssessmentServicesProvider>(handle).getAssessmentItems(ownerUri, targetType);
|
||||
}
|
||||
|
||||
public $generateAssessmentScript(handle: number, items: azdata.SqlAssessmentResultItem[]): Thenable<azdata.ResultStatus> {
|
||||
return this._resolveProvider<azdata.SqlAssessmentServicesProvider>(handle).generateAssessmentScript(items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,6 +364,10 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
|
||||
return extHostDataProvider.$registerSerializationProvider(provider);
|
||||
};
|
||||
|
||||
let registerSqlAssessmentServicesProvider = (provider: azdata.SqlAssessmentServicesProvider): vscode.Disposable => {
|
||||
return extHostDataProvider.$registerSqlAssessmentServiceProvider(provider);
|
||||
};
|
||||
|
||||
// namespace: dataprotocol
|
||||
const dataprotocol: typeof azdata.dataprotocol = {
|
||||
registerBackupProvider,
|
||||
@@ -382,6 +386,7 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
|
||||
registerAgentServicesProvider,
|
||||
registerCapabilitiesServiceProvider,
|
||||
registerSerializationProvider,
|
||||
registerSqlAssessmentServicesProvider,
|
||||
onDidChangeLanguageFlavor(listener: (e: azdata.DidChangeLanguageFlavorParams) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {
|
||||
return extHostDataProvider.onDidChangeLanguageFlavor(listener, thisArgs, disposables);
|
||||
},
|
||||
@@ -510,6 +515,11 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
|
||||
NotebookChangeKind: sqlExtHostTypes.NotebookChangeKind
|
||||
};
|
||||
|
||||
const sqlAssessment: typeof azdata.sqlAssessment = {
|
||||
SqlAssessmentResultItemKind: sqlExtHostTypes.SqlAssessmentResultItemKind,
|
||||
SqlAssessmentTargetType: sqlExtHostTypes.SqlAssessmentTargetType
|
||||
};
|
||||
|
||||
return {
|
||||
accounts,
|
||||
connection,
|
||||
@@ -556,7 +566,8 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
|
||||
ExtensionNodeType: sqlExtHostTypes.ExtensionNodeType,
|
||||
ColumnSizingMode: sqlExtHostTypes.ColumnSizingMode,
|
||||
DatabaseEngineEdition: sqlExtHostTypes.DatabaseEngineEdition,
|
||||
TabOrientation: sqlExtHostTypes.TabOrientation
|
||||
TabOrientation: sqlExtHostTypes.TabOrientation,
|
||||
sqlAssessment
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -488,6 +488,26 @@ export abstract class ExtHostDataProtocolShape {
|
||||
* Serialization continuation request
|
||||
*/
|
||||
$continueSerialization(handle: number, requestParams: azdata.SerializeDataContinueRequestParams): Thenable<azdata.SerializeDataResult> { throw ni(); }
|
||||
|
||||
|
||||
/**
|
||||
* SQL Assessment Section
|
||||
*/
|
||||
|
||||
/**
|
||||
* Perform an assessment
|
||||
*/
|
||||
$assessmentInvoke(handle: number, connectionUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* Get applicable assessment rules
|
||||
*/
|
||||
$getAssessmentItems(handle: number, connectionUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* Generate an assessment script based on recent results
|
||||
*/
|
||||
$generateAssessmentScript(handle: number, items: azdata.SqlAssessmentResultItem[]): Thenable<azdata.ResultStatus> { throw ni(); }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,6 +571,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
|
||||
$registerAdminServicesProvider(providerId: string, handle: number): Promise<any>;
|
||||
$registerAgentServicesProvider(providerId: string, handle: number): Promise<any>;
|
||||
$registerSerializationProvider(providerId: string, handle: number): Promise<any>;
|
||||
$registerSqlAssessmentServicesProvider(providerId: string, handle: number): Promise<any>;
|
||||
$unregisterProvider(handle: number): Promise<any>;
|
||||
$onConnectionComplete(handle: number, connectionInfoSummary: azdata.ConnectionInfoSummary): void;
|
||||
$onIntelliSenseCacheComplete(handle: number, connectionUri: string): void;
|
||||
|
||||
@@ -350,7 +350,8 @@ export enum DataProviderType {
|
||||
CapabilitiesProvider = 'CapabilitiesProvider',
|
||||
ObjectExplorerNodeProvider = 'ObjectExplorerNodeProvider',
|
||||
SerializationProvider = 'SerializationProvider',
|
||||
IconProvider = 'IconProvider'
|
||||
IconProvider = 'IconProvider',
|
||||
SqlAssessmentServicesProvider = 'SqlAssessmentServicesProvider'
|
||||
}
|
||||
|
||||
export enum DeclarativeDataType {
|
||||
@@ -847,12 +848,12 @@ export interface TabbedPanelLayout {
|
||||
alwaysShowTabs: boolean;
|
||||
}
|
||||
|
||||
export const enum SqlAssessmentTargetType {
|
||||
export enum SqlAssessmentTargetType {
|
||||
Server = 1,
|
||||
Database = 2
|
||||
}
|
||||
|
||||
export const enum SqlAssessmentResultItemKind {
|
||||
export enum SqlAssessmentResultItemKind {
|
||||
RealResult = 0,
|
||||
Warning = 1,
|
||||
Error = 2
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
<div #actionbarContainer class="asmt-actionbar-container"></div>
|
||||
<div #resultsgrid class="asmtview-grid"></div>
|
||||
@@ -0,0 +1,627 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/asmt';
|
||||
import 'vs/css!./media/detailview';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as azdata from 'azdata';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy, AfterContentChecked } from '@angular/core';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { AsmtViewComponent } from 'sql/workbench/contrib/assessment/browser/asmtView.component';
|
||||
import { HeaderFilter } from 'sql/base/browser/ui/table/plugins/headerFilter.plugin';
|
||||
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { attachButtonStyler } from 'sql/platform/theme/common/styler';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
import { RowDetailView, ExtendedItem } from 'sql/base/browser/ui/table/plugins/rowDetailView';
|
||||
import {
|
||||
IAssessmentComponent,
|
||||
IAsmtActionInfo,
|
||||
AsmtServerSelectItemsAction,
|
||||
AsmtServerInvokeItemsAction,
|
||||
AsmtDatabaseSelectItemsAction,
|
||||
AsmtDatabaseInvokeItemsAction,
|
||||
AsmtExportAsScriptAction,
|
||||
AsmtSamplesLinkAction
|
||||
} from 'sql/workbench/contrib/assessment/common/asmtActions';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import * as Utils from 'sql/platform/connection/common/utils';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { AssessmentType, TARGET_ICON_CLASS } from 'sql/workbench/contrib/assessment/common/consts';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
import { ITableStyles } from 'sql/base/browser/ui/table/interfaces';
|
||||
import { TelemetryView } from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
|
||||
export const ASMTRESULTSVIEW_SELECTOR: string = 'asmt-results-view-component';
|
||||
export const ROW_HEIGHT: number = 25;
|
||||
export const ACTIONBAR_PADDING: number = 10;
|
||||
|
||||
const PLACEHOLDER_LABEL = nls.localize('asmt.NoResultsInitial', "Nothing to show. Invoke assessment to get results");
|
||||
const COLUMN_MESSAGE_ID: string = 'message';
|
||||
|
||||
const COLUMN_MESSAGE_TITLE: { [mode: number]: string } = {
|
||||
[AssessmentType.AvailableRules]: nls.localize('asmt.column.displayName', "Display Name"),
|
||||
[AssessmentType.InvokeAssessment]: nls.localize('asmt.column.message', "Message"),
|
||||
};
|
||||
|
||||
enum AssessmentResultItemKind {
|
||||
RealResult = 0,
|
||||
Warning = 1,
|
||||
Error = 2
|
||||
}
|
||||
|
||||
const KIND_CLASS: { [kind: number]: string } = {
|
||||
[AssessmentResultItemKind.Error]: 'error-val',
|
||||
[AssessmentResultItemKind.Warning]: 'warning-val',
|
||||
[AssessmentResultItemKind.RealResult]: ''
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: ASMTRESULTSVIEW_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./asmtResultsView.component.html')),
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => AsmtResultsViewComponent) }],
|
||||
})
|
||||
|
||||
export class AsmtResultsViewComponent extends TabChild implements IAssessmentComponent, OnInit, OnDestroy, AfterContentChecked {
|
||||
protected _parentComponent: AsmtViewComponent;
|
||||
protected _table: Table<any>;
|
||||
protected _visibilityElement: ElementRef;
|
||||
protected isVisible: boolean = false;
|
||||
protected isInitialized: boolean = false;
|
||||
protected isRefreshing: boolean = false;
|
||||
protected _actionBar: Taskbar;
|
||||
|
||||
private columns: Array<Slick.Column<any>> = [
|
||||
{
|
||||
name: nls.localize('asmt.column.target', "Target"),
|
||||
formatter: this.renderTarget,
|
||||
field: 'targetName',
|
||||
width: 80,
|
||||
id: 'target'
|
||||
},
|
||||
{ name: nls.localize('asmt.column.severity', "Serverity"), field: 'severity', maxWidth: 90, id: 'severity' },
|
||||
{
|
||||
name: nls.localize('asmt.column.message', "Message"),
|
||||
field: 'message',
|
||||
width: 300,
|
||||
id: COLUMN_MESSAGE_ID,
|
||||
formatter: (_row, _cell, _value, _columnDef, dataContext) => this.appendHelplink(dataContext.message, dataContext.helpLink, dataContext.kind, this.wrapByKind),
|
||||
},
|
||||
{
|
||||
name: nls.localize('asmt.column.tags', "Tags"),
|
||||
field: 'tags',
|
||||
width: 80,
|
||||
id: 'tags',
|
||||
formatter: (row, cell, value, columnDef, dataContext) => this.renderTags(row, cell, value, columnDef, dataContext)
|
||||
},
|
||||
{ name: nls.localize('asmt.column.checkId', "Check ID"), field: 'checkId', maxWidth: 140, id: 'checkId' }
|
||||
];
|
||||
private dataView: any;
|
||||
private filterPlugin: any;
|
||||
private isServerMode: boolean;
|
||||
private rowDetail: RowDetailView<Slick.SlickData>;
|
||||
private exportActionItem: IAction;
|
||||
private placeholderElem: HTMLElement;
|
||||
private placeholderNoResultsLabel: string;
|
||||
private spinner: { [mode: number]: HTMLElement } = Object.create(null);
|
||||
private lastInvokedResults: azdata.SqlAssessmentResultItem[];
|
||||
|
||||
@ViewChild('resultsgrid') _gridEl: ElementRef;
|
||||
@ViewChild('actionbarContainer') protected actionBarContainer: ElementRef;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => AsmtViewComponent)) private _asmtViewComponent: AsmtViewComponent,
|
||||
@Inject(IWorkbenchThemeService) private readonly _themeService: IWorkbenchThemeService,
|
||||
@Inject(IWorkbenchLayoutService) private readonly layoutService: IWorkbenchLayoutService,
|
||||
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
|
||||
@Inject(IDashboardService) _dashboardService: IDashboardService,
|
||||
@Inject(IAdsTelemetryService) private _telemetryService: IAdsTelemetryService,
|
||||
@Inject(ILogService) protected _logService: ILogService
|
||||
) {
|
||||
super();
|
||||
let self = this;
|
||||
let profile = this._commonService.connectionManagementService.connectionInfo.connectionProfile;
|
||||
|
||||
this.isServerMode = !profile.databaseName || Utils.isMaster(profile);
|
||||
|
||||
if (this.isServerMode) {
|
||||
this.placeholderNoResultsLabel = nls.localize('asmt.TargetInstanceComplient', "Instance {0} is totally compliant with the best practices. Good job!", profile.serverName);
|
||||
} else {
|
||||
this.placeholderNoResultsLabel = nls.localize('asmt.TargetDatabaseComplient', "Database {0} is totally compliant with the best practices. Good job!", profile.databaseName);
|
||||
}
|
||||
|
||||
this._register(_dashboardService.onLayout(d => self.layout()));
|
||||
this._register(_themeService.onDidColorThemeChange(this._updateStyles, this));
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._visibilityElement = this._gridEl;
|
||||
this._parentComponent = this._asmtViewComponent;
|
||||
this._telemetryService.sendViewEvent(TelemetryView.SqlAssessment);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.isVisible = false;
|
||||
}
|
||||
|
||||
ngAfterContentChecked(): void {
|
||||
if (this._visibilityElement && this._parentComponent) {
|
||||
if (this.isVisible === false && this._visibilityElement.nativeElement.offsetParent !== null) {
|
||||
this.isVisible = true;
|
||||
if (!this.isInitialized) {
|
||||
this.initializeComponent();
|
||||
this.layout();
|
||||
this.isInitialized = true;
|
||||
}
|
||||
} else if (this.isVisible === true && this._visibilityElement.nativeElement.offsetParent === null) {
|
||||
this.isVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get resultItems(): azdata.SqlAssessmentResultItem[] {
|
||||
return this.lastInvokedResults;
|
||||
}
|
||||
|
||||
public get isActive(): boolean {
|
||||
return this.isVisible;
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
let statusBar = this.layoutService.getContainer(Parts.STATUSBAR_PART);
|
||||
if (dom.isInDOM(this.actionBarContainer.nativeElement) && dom.isInDOM(statusBar)) {
|
||||
let toolbarBottom = this.actionBarContainer.nativeElement.getBoundingClientRect().bottom + ACTIONBAR_PADDING;
|
||||
let statusTop = statusBar.getBoundingClientRect().top;
|
||||
this._table.layout(new dom.Dimension(
|
||||
dom.getContentWidth(this._gridEl.nativeElement),
|
||||
statusTop - toolbarBottom));
|
||||
|
||||
let gridCanvasWidth = this._table.grid.getCanvasNode().clientWidth;
|
||||
let placeholderWidth = dom.getDomNodePagePosition(this.placeholderElem).width;
|
||||
dom.position(this.placeholderElem, null, null, null, (gridCanvasWidth - placeholderWidth) / 2, 'relative');
|
||||
|
||||
this._updateStyles(this._themeService.getColorTheme());
|
||||
}
|
||||
}
|
||||
|
||||
public showProgress(mode: AssessmentType) {
|
||||
this.spinner[mode].style.visibility = 'visible';
|
||||
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public showInitialResults(result: azdata.SqlAssessmentResult, method: AssessmentType) {
|
||||
if (result) {
|
||||
if (method === AssessmentType.InvokeAssessment) {
|
||||
this.lastInvokedResults = result.items;
|
||||
} else {
|
||||
this.lastInvokedResults = [];
|
||||
}
|
||||
|
||||
this.displayResults(result.items, method);
|
||||
if (result.items.length > 0) {
|
||||
this._asmtViewComponent.displayAssessmentInfo(result.apiVersion, result.items[0].rulesetVersion);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
this._table.grid.invalidate();
|
||||
}
|
||||
|
||||
public appendResults(result: azdata.SqlAssessmentResult, method: AssessmentType) {
|
||||
if (method === AssessmentType.InvokeAssessment) {
|
||||
this.lastInvokedResults.push(...result.items);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
this.dataView.beginUpdate();
|
||||
result.items.forEach((asmtResult, index) => {
|
||||
this.dataView.addItem(this.convertToDataViewItems(asmtResult, index, method));
|
||||
});
|
||||
|
||||
this.dataView.reSort();
|
||||
this.dataView.endUpdate();
|
||||
this.dataView.refresh();
|
||||
this._table.autosizeColumns();
|
||||
this._table.resizeCanvas();
|
||||
}
|
||||
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
this._table.grid.invalidate();
|
||||
}
|
||||
|
||||
public stopProgress(mode: AssessmentType) {
|
||||
this.spinner[mode].style.visibility = 'hidden';
|
||||
if (this.isVisible) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private initializeComponent() {
|
||||
let columns = this.columns.map((column) => {
|
||||
column.rerenderOnResize = true;
|
||||
return column;
|
||||
});
|
||||
let options = <Slick.GridOptions<any>>{
|
||||
syncColumnCellResize: true,
|
||||
enableColumnReorder: false,
|
||||
rowHeight: ROW_HEIGHT,
|
||||
enableCellNavigation: true,
|
||||
forceFitColumns: false
|
||||
};
|
||||
|
||||
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
|
||||
|
||||
let rowDetail = new RowDetailView({
|
||||
cssClass: '_detail_selector',
|
||||
process: (item) => {
|
||||
(<any>rowDetail).onAsyncResponse.notify({
|
||||
'itemDetail': item,
|
||||
}, undefined, this);
|
||||
},
|
||||
useRowClick: true,
|
||||
panelRows: 2,
|
||||
postTemplate: (itemDetail) => this.appendHelplink(itemDetail.description, itemDetail.helpLink, itemDetail.kind, this.wrapByKind),
|
||||
preTemplate: () => '',
|
||||
loadOnce: true
|
||||
});
|
||||
|
||||
this.rowDetail = rowDetail;
|
||||
let columnDef = this.rowDetail.getColumnDefinition();
|
||||
columnDef.formatter = (row, cell, value, columnDef, dataContext) => this.detailSelectionFormatter(row, cell, value, columnDef, dataContext as ExtendedItem<Slick.SlickData>);
|
||||
columns.unshift(columnDef);
|
||||
|
||||
let filterPlugin = new HeaderFilter<Slick.SlickData>();
|
||||
this._register(attachButtonStyler(filterPlugin, this._themeService));
|
||||
this.filterPlugin = filterPlugin;
|
||||
this.filterPlugin.onFilterApplied.subscribe((e, args) => {
|
||||
let filterValues = args.column.filterValues;
|
||||
if (filterValues) {
|
||||
this.dataView.refresh();
|
||||
this._table.grid.resetActiveCell();
|
||||
}
|
||||
});
|
||||
this.filterPlugin.onCommand.subscribe((e, args: any) => {
|
||||
this.columnSort(args.column.field, args.command === 'sort-asc');
|
||||
});
|
||||
|
||||
// we need to be able to show distinct array values in filter dialog for columns with array data
|
||||
filterPlugin['getFilterValues'] = this.getFilterValues;
|
||||
filterPlugin['getAllFilterValues'] = this.getAllFilterValues;
|
||||
filterPlugin['getFilterValuesByInput'] = this.getFilterValuesByInput;
|
||||
|
||||
dom.clearNode(this._gridEl.nativeElement);
|
||||
dom.clearNode(this.actionBarContainer.nativeElement);
|
||||
|
||||
|
||||
if (this.isServerMode) {
|
||||
this.initActionBar(
|
||||
this._register(this._instantiationService.createInstance(AsmtServerInvokeItemsAction)),
|
||||
this._register(this._instantiationService.createInstance(AsmtServerSelectItemsAction)));
|
||||
} else {
|
||||
let connectionInfo = this._commonService.connectionManagementService.connectionInfo;
|
||||
let databaseSelectAsmt = this._register(this._instantiationService.createInstance(AsmtDatabaseSelectItemsAction, connectionInfo.connectionProfile.databaseName));
|
||||
let databaseInvokeAsmt = this._register(this._instantiationService.createInstance(AsmtDatabaseInvokeItemsAction, connectionInfo.connectionProfile.databaseName));
|
||||
this.initActionBar(databaseInvokeAsmt, databaseSelectAsmt);
|
||||
}
|
||||
|
||||
this._table = this._register(new Table(this._gridEl.nativeElement, { columns }, options));
|
||||
this._table.grid.setData(this.dataView, true);
|
||||
this._table.registerPlugin(<any>this.rowDetail);
|
||||
this._table.registerPlugin(filterPlugin);
|
||||
|
||||
|
||||
this.placeholderElem = document.createElement('span');
|
||||
this.placeholderElem.className = 'placeholder';
|
||||
this.placeholderElem.innerText = PLACEHOLDER_LABEL;
|
||||
dom.append(this._table.grid.getCanvasNode(), this.placeholderElem);
|
||||
}
|
||||
|
||||
private initActionBar(invokeAction: IAction, selectAction: IAction) {
|
||||
this.exportActionItem = this._register(this._instantiationService.createInstance(AsmtExportAsScriptAction));
|
||||
|
||||
let taskbar = <HTMLElement>this.actionBarContainer.nativeElement;
|
||||
this._actionBar = this._register(new Taskbar(taskbar));
|
||||
this.spinner[AssessmentType.InvokeAssessment] = Taskbar.createTaskbarSpinner();
|
||||
this.spinner[AssessmentType.AvailableRules] = Taskbar.createTaskbarSpinner();
|
||||
|
||||
this._actionBar.setContent([
|
||||
{ action: invokeAction },
|
||||
{ element: this.spinner[AssessmentType.InvokeAssessment] },
|
||||
{ action: selectAction },
|
||||
{ element: this.spinner[AssessmentType.AvailableRules] },
|
||||
{ action: this.exportActionItem },
|
||||
{ action: this._instantiationService.createInstance(AsmtSamplesLinkAction) }
|
||||
]);
|
||||
|
||||
let connectionInfo = this._commonService.connectionManagementService.connectionInfo;
|
||||
let context: IAsmtActionInfo = { component: this, ownerUri: Utils.generateUri(connectionInfo.connectionProfile.clone(), 'dashboard'), connectionId: connectionInfo.connectionProfile.id };
|
||||
this._actionBar.context = context;
|
||||
this.exportActionItem.enabled = false;
|
||||
}
|
||||
|
||||
private convertToDataViewItems(asmtResult: azdata.SqlAssessmentResultItem, index: number, method: AssessmentType) {
|
||||
return {
|
||||
id: `${asmtResult.targetType}${this.escapeId(asmtResult.targetName)}${asmtResult.checkId}${index}`,
|
||||
severity: asmtResult.level,
|
||||
message: method === AssessmentType.InvokeAssessment ? asmtResult.message : asmtResult.displayName,
|
||||
tags: this.clearOutDefaultRuleset(asmtResult.tags),
|
||||
checkId: asmtResult.checkId,
|
||||
targetName: asmtResult.targetName,
|
||||
targetType: asmtResult.targetType,
|
||||
helpLink: asmtResult.helpLink,
|
||||
description: method === AssessmentType.InvokeAssessment ? asmtResult.message : asmtResult.description,
|
||||
mode: method,
|
||||
kind: asmtResult.kind !== undefined ? asmtResult.kind : AssessmentResultItemKind.RealResult
|
||||
};
|
||||
}
|
||||
|
||||
private displayResults(results: azdata.SqlAssessmentResultItem[], method: AssessmentType) {
|
||||
this._table.grid.updateColumnHeader(COLUMN_MESSAGE_ID, COLUMN_MESSAGE_TITLE[method]);
|
||||
|
||||
let resultViews = results.map((item, index) => this.convertToDataViewItems(item, index, method));
|
||||
|
||||
this.dataView.beginUpdate();
|
||||
this.dataView.setItems(resultViews);
|
||||
this.dataView.setFilter((item) => this.filter(item));
|
||||
this.dataView.endUpdate();
|
||||
this.dataView.refresh();
|
||||
|
||||
this._table.autosizeColumns();
|
||||
this._table.resizeCanvas();
|
||||
this.exportActionItem.enabled = (results.length > 0 && method === AssessmentType.InvokeAssessment);
|
||||
|
||||
if (results.length > 0) {
|
||||
dom.hide(this.placeholderElem);
|
||||
} else {
|
||||
this.placeholderElem.innerText = this.placeholderNoResultsLabel;
|
||||
}
|
||||
}
|
||||
|
||||
private escapeId(value: string): string {
|
||||
return escape(value).replace(/[*//]/g, function (match) {
|
||||
switch (match) {
|
||||
case '*':
|
||||
case '/':
|
||||
return '_';
|
||||
default:
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private clearOutDefaultRuleset(tags: string[]): string[] {
|
||||
let idx = tags.indexOf('DefaultRuleset');
|
||||
if (idx > -1) {
|
||||
tags.splice(idx, 1);
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
private columnSort(field: string, isAscending: boolean) {
|
||||
this.dataView.sort((item1, item2) => {
|
||||
if (item1.checkId === undefined || item2.checkId === undefined) {
|
||||
return;
|
||||
}
|
||||
switch (field) {
|
||||
case 'tags':
|
||||
return item1.tags.toString().localeCompare(item2.tags.toString());
|
||||
case 'targetName':
|
||||
if (item1.targetType > item2.targetType) {
|
||||
return 1;
|
||||
} else if (item1.targetType < item2.targetType) {
|
||||
return -1;
|
||||
} else {
|
||||
return item1.targetName.localeCompare(item2.targetName);
|
||||
}
|
||||
}
|
||||
return item1[field].localeCompare(item2[field]);
|
||||
}, isAscending);
|
||||
}
|
||||
|
||||
private filter(item: any) {
|
||||
let columns = this._table.grid.getColumns();
|
||||
let value = true;
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
let col: any = columns[i];
|
||||
let filterValues = col.filterValues;
|
||||
if (filterValues && filterValues.length > 0) {
|
||||
if (item._parent) {
|
||||
value = value && find(filterValues, x => x === item._parent[col.field]);
|
||||
} else {
|
||||
let colValue = item[col.field];
|
||||
if (colValue instanceof Array) {
|
||||
value = value && find(filterValues, x => colValue.indexOf(x) >= 0);
|
||||
} else {
|
||||
value = value && find(filterValues, x => x === colValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private wrapByKind(kind: AssessmentResultItemKind, element: string): string {
|
||||
if (kind !== AssessmentResultItemKind.RealResult) {
|
||||
return `<span class='excl ${KIND_CLASS[kind]}'>${element}</span>`;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
private appendHelplink(msg: string, helpLink: string, kind: AssessmentResultItemKind, wrapByKindFunc): string {
|
||||
if (msg !== undefined) {
|
||||
return `${wrapByKindFunc(kind, escape(msg))}<a class='helpLink' href='${helpLink}' \>${nls.localize('asmt.learnMore', "Learn More")}</a>`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
private renderTags(_row, _cell, _value, _columnDef, dataContext) {
|
||||
if (dataContext.tags !== undefined) {
|
||||
return dataContext.tags.join(`, `);
|
||||
}
|
||||
return dataContext.tags;
|
||||
}
|
||||
|
||||
private renderTarget(_row, _cell, _value, _columnDef, dataContext) {
|
||||
return `<div class='carbon-taskbar'><span class='action-label codicon ${TARGET_ICON_CLASS[dataContext.targetType]}'>${dataContext.targetName}</span></div>`;
|
||||
}
|
||||
|
||||
private detailSelectionFormatter(_row: number, _cell: number, _value: any, _columnDef: Slick.Column<Slick.SlickData>, dataContext: Slick.SlickData): string | undefined {
|
||||
|
||||
if (dataContext._collapsed === undefined) {
|
||||
dataContext._collapsed = true;
|
||||
dataContext._sizePadding = 0; //the required number of pading rows
|
||||
dataContext._height = 0; //the actual height in pixels of the detail field
|
||||
dataContext._isPadding = false;
|
||||
dataContext._parent = undefined;
|
||||
}
|
||||
|
||||
if (dataContext._isPadding === true) {
|
||||
//render nothing
|
||||
} else if (dataContext._collapsed) {
|
||||
return '<div class=\'detailView-toggle expand\'></div>';
|
||||
} else {
|
||||
const html: Array<string> = [];
|
||||
const rowHeight = ROW_HEIGHT;
|
||||
const bottomMargin = 5;
|
||||
html.push('<div class="detailView-toggle collapse"></div></div>');
|
||||
|
||||
html.push(`<div id='cellDetailView_${dataContext.id}' class='dynamic-cell-detail dynamic-cell-detail-color' `); //apply custom css to detail
|
||||
html.push(`style=\'height:${dataContext._height}px;`); //set total height of padding
|
||||
html.push(`top:${rowHeight}px'>`); //shift detail below 1st row
|
||||
html.push(`<div id='detailViewContainer_${dataContext.id}"' class='detail-container' style='max-height:${(dataContext._height! - rowHeight + bottomMargin)}px'>`); //sub ctr for custom styling
|
||||
html.push(`<div id='innerDetailView_${dataContext.id}'>${dataContext._detailContent!}</div></div>`);
|
||||
return html.join('');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getFilterValues(dataView: Slick.DataProvider<Slick.SlickData>, column: Slick.Column<any>): Array<any> {
|
||||
const seen: Array<string> = [];
|
||||
for (let i = 0; i < dataView.getLength(); i++) {
|
||||
const value = dataView.getItem(i)[column.field!];
|
||||
if (value instanceof Array) {
|
||||
for (let item = 0; item < value.length; item++) {
|
||||
if (!seen.some(x => x === value[item])) {
|
||||
seen.push(value[item]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!seen.some(x => x === value)) {
|
||||
seen.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return seen;
|
||||
}
|
||||
|
||||
private getAllFilterValues(data: Array<Slick.SlickData>, column: Slick.Column<any>) {
|
||||
const seen: Array<any> = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const value = data[i][column.field!];
|
||||
if (value instanceof Array) {
|
||||
for (let item = 0; item < value.length; item++) {
|
||||
if (!seen.some(x => x === value[item])) {
|
||||
seen.push(value[item]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!seen.some(x => x === value)) {
|
||||
seen.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seen.sort((v) => { return v; });
|
||||
}
|
||||
|
||||
private getFilterValuesByInput($input: JQuery<HTMLElement>): Array<string> {
|
||||
const column = $input.data('column'),
|
||||
filter = $input.val() as string,
|
||||
dataView = this['grid'].getData() as Slick.DataProvider<Slick.SlickData>,
|
||||
seen: Array<any> = [];
|
||||
|
||||
for (let i = 0; i < dataView.getLength(); i++) {
|
||||
const value = dataView.getItem(i)[column.field];
|
||||
if (value instanceof Array) {
|
||||
if (filter.length > 0) {
|
||||
const itemValue = !value ? [] : value;
|
||||
const lowercaseFilter = filter.toString().toLowerCase();
|
||||
const lowercaseVals = itemValue.map(v => v.toLowerCase());
|
||||
for (let valIdx = 0; valIdx < value.length; valIdx++) {
|
||||
if (!seen.some(x => x === value[valIdx]) && lowercaseVals[valIdx].indexOf(lowercaseFilter) > -1) {
|
||||
seen.push(value[valIdx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let item = 0; item < value.length; item++) {
|
||||
if (!seen.some(x => x === value[item])) {
|
||||
seen.push(value[item]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (filter.length > 0) {
|
||||
const itemValue = !value ? '' : value;
|
||||
const lowercaseFilter = filter.toString().toLowerCase();
|
||||
const lowercaseVal = itemValue.toString().toLowerCase();
|
||||
|
||||
if (!seen.some(x => x === value) && lowercaseVal.indexOf(lowercaseFilter) > -1) {
|
||||
seen.push(value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!seen.some(x => x === value)) {
|
||||
seen.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seen.sort((v) => { return v; });
|
||||
}
|
||||
|
||||
private _updateStyles(theme: IColorTheme): void {
|
||||
this.actionBarContainer.nativeElement.style.borderTopColor = theme.getColor(themeColors.DASHBOARD_BORDER, true).toString();
|
||||
let tableStyle: ITableStyles = {
|
||||
tableHeaderBackground: theme.getColor(themeColors.PANEL_BACKGROUND)
|
||||
};
|
||||
this._table.style(tableStyle);
|
||||
const rowExclSelector = '.asmtview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row';
|
||||
dom.removeCSSRulesContainingSelector(`${rowExclSelector} .${KIND_CLASS[AssessmentResultItemKind.Error]}`);
|
||||
dom.createCSSRule(`${rowExclSelector} .${KIND_CLASS[AssessmentResultItemKind.Error]}`, `color: ${theme.getColor(themeColors.NOTIFICATIONS_ERROR_ICON_FOREGROUND).toString()}`);
|
||||
|
||||
dom.removeCSSRulesContainingSelector(`${rowExclSelector} .${KIND_CLASS[AssessmentResultItemKind.Warning]}`);
|
||||
dom.createCSSRule(`${rowExclSelector} .${KIND_CLASS[AssessmentResultItemKind.Warning]}`, `color: ${theme.getColor(themeColors.NOTIFICATIONS_WARNING_ICON_FOREGROUND).toString()}`);
|
||||
|
||||
const detailRowSelector = '.asmtview-grid .grid-canvas > .ui-widget-content.slick-row .dynamic-cell-detail-color';
|
||||
dom.removeCSSRulesContainingSelector(detailRowSelector);
|
||||
dom.createCSSRule(detailRowSelector, `background-color: ${theme.getColor(themeColors.EDITOR_GROUP_HEADER_TABS_BACKGROUND).toString()}`);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<div id="asmtViewDiv" class="fullsize">
|
||||
<div style="margin: 10px 0px 0px 10px;">
|
||||
<div style="float: left; margin-right: 30px;">
|
||||
<div class="propertiesSectionTitle">
|
||||
<span>{{localizedStrings.SECTION_TITLE_API}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="propertyBlock">
|
||||
<div>
|
||||
<span class="propertyLabel">{{localizedStrings.API_VERSION}}</span>
|
||||
<span>{{api}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="propertyLabel">{{localizedStrings.DEFAULT_RULESET_VERSION}}</span>
|
||||
<span>{{ruleset}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="propertiesSectionTitle">
|
||||
<span>{{localizedStrings.SECTION_TITLE_SQL_SERVER}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="propertyBlock">
|
||||
<div>
|
||||
<span class="propertyLabel">{{localizedStrings.SERVER_VERSION}}</span>
|
||||
<span>{{connectionInfo.serverVersion}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="propertyLabel">{{localizedStrings.SERVER_EDITION}}</span>
|
||||
<span>{{connectionInfo.serverEdition}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="propertyBlock">
|
||||
<div>
|
||||
<span class="propertyLabel">{{localizedStrings.SERVER_INSTANCENAME}}</span>
|
||||
<span>{{instanceName}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="propertyLabel">{{localizedStrings.SERVER_OSVERSION}}</span>
|
||||
<span>{{connectionInfo.osVersion}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div #asmtresultcomponent id="asmtDiv" class="fullsize">
|
||||
<asmt-results-view-component></asmt-results-view-component>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/asmt';
|
||||
import { Component, Inject, forwardRef, ChangeDetectorRef, ViewChild, Injectable, OnInit } from '@angular/core';
|
||||
import { ServerInfo } from 'azdata';
|
||||
//import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
|
||||
import { AngularDisposable } from 'sql/base/browser/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
|
||||
import { AsmtResultsViewComponent } from 'sql/workbench/contrib/assessment/browser/asmtResultsView.component';
|
||||
|
||||
|
||||
const LocalizedStrings = {
|
||||
SECTION_TITLE_API: localize('asmt.section.api.title', "API information"),
|
||||
API_VERSION: localize('asmt.apiversion', "API Version:"),
|
||||
DEFAULT_RULESET_VERSION: localize('asmt.rulesetversion', "Default Ruleset Version:"),
|
||||
SECTION_TITLE_SQL_SERVER: localize('asmt.section.instance.title', "SQL Server Instance Details"),
|
||||
SERVER_VERSION: localize('asmt.serverversion', "Version:"),
|
||||
SERVER_EDITION: localize('asmt.serveredition', "Edition:"),
|
||||
SERVER_INSTANCENAME: localize('asmt.instancename', "Instance Name:"),
|
||||
SERVER_OSVERSION: localize('asmt.osversion', "OS Version:")
|
||||
};
|
||||
|
||||
export const DASHBOARD_SELECTOR: string = 'asmtview-component';
|
||||
|
||||
@Component({
|
||||
selector: DASHBOARD_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./asmtView.component.html'))
|
||||
})
|
||||
@Injectable()
|
||||
export class AsmtViewComponent extends AngularDisposable implements OnInit {
|
||||
|
||||
@ViewChild('asmtresultcomponent') private _asmtResultView: AsmtResultsViewComponent;
|
||||
protected localizedStrings = LocalizedStrings;
|
||||
|
||||
connectionInfo: ServerInfo = null;
|
||||
instanceName: string = '';
|
||||
ruleset: string = '';
|
||||
api: string = '';
|
||||
|
||||
|
||||
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.displayConnectionInfo();
|
||||
}
|
||||
|
||||
private displayConnectionInfo() {
|
||||
this.connectionInfo = this._commonService.connectionManagementService.connectionInfo.serverInfo;
|
||||
let serverName = this._commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
|
||||
let machineName = this.connectionInfo['machineName'];
|
||||
if ((['local', '(local)', '(local);'].indexOf(serverName.toLowerCase()) >= 0) || machineName.toLowerCase() === serverName.toLowerCase()) {
|
||||
this.instanceName = machineName;
|
||||
}
|
||||
else {
|
||||
this.instanceName = machineName + '\\' + serverName;
|
||||
}
|
||||
}
|
||||
|
||||
public displayAssessmentInfo(apiVersion: string, rulesetVersion: string) {
|
||||
this.api = apiVersion;
|
||||
this.ruleset = rulesetVersion;
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
public layout() {
|
||||
this._asmtResultView.layout();
|
||||
//this._panel.layout();
|
||||
}
|
||||
}
|
||||
132
src/sql/workbench/contrib/assessment/browser/media/asmt.css
Normal file
@@ -0,0 +1,132 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
asmtview-component {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.asmt-heading {
|
||||
font-weight: bold;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
#asmtViewDiv .propertiesSectionTitle {
|
||||
margin-bottom: 20px;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
#asmtViewDiv .propertyBlock {
|
||||
display: inline-block;
|
||||
margin: 0px 0px 20px 20px;
|
||||
}
|
||||
|
||||
#asmtViewDiv .propertyLabel {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.asmt-actionbar-container {
|
||||
padding-bottom: 10px;
|
||||
border-top: 3px solid;
|
||||
}
|
||||
|
||||
.asmtview-grid {
|
||||
height: calc(100% - 75px);
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#asmtViewDiv .slick-header-column {
|
||||
border: 0px !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.asmtview-grid>.monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted;
|
||||
}
|
||||
|
||||
.asmtview-grid .grid-canvas>.ui-widget-content.slick-row>.slick-cell {
|
||||
cursor: pointer;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.asmtview-grid>.monaco-table .slick-viewport>.grid-canvas>.ui-widget-content.slick-row .slick-cell>.excl,
|
||||
.asmtview-grid .detail-container .excl {
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#asmtDiv .detail {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#asmtDiv .preload {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#asmtDiv .codicon.in-progress {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
#asmtDiv .carbon-taskbar .action-item {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.asmt-actionbar-container .monaco-action-bar>ul.actions-container>li.action-item {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.asmt-actionbar-container .actions-container .action-item .action-label {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
asmtview-component .asmtview-grid .slick-cell.error-row {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
#asmtDiv asmtview-component .monaco-toolbar.carbon-taskbar {
|
||||
margin: 10px 0px 10px 0px;
|
||||
}
|
||||
|
||||
#asmtDiv .helpLink {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.vs asmtview-component .action-label.codicon.exportAsScriptIcon {
|
||||
background-image: url("newquery.svg");
|
||||
}
|
||||
|
||||
.vs-dark asmtview-component .action-label.codicon.exportAsScriptIcon,
|
||||
.hc-black asmtview-component .action-label.codicon.exportAsScriptIcon {
|
||||
background-image: url("newquery_inverse.svg");
|
||||
}
|
||||
|
||||
.vs asmtview-component .action-label.codicon.asmt-learnmore {
|
||||
background-image: url("configuredashboard.svg");
|
||||
}
|
||||
|
||||
.vs-dark asmtview-component .action-label.codicon.asmt-learnmore,
|
||||
.hc-black asmtview-component .action-label.codicon.asmt-learnmore {
|
||||
background-image: url("configuredashboard_inverse.svg");
|
||||
}
|
||||
|
||||
.asmtview-grid>.monaco-table .slick-viewport>.grid-canvas>.ui-widget-content.slick-row .slick-cell .codicon {
|
||||
background-position: left;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.asmt-actionbar-container .action-item>.action-label.codicon.database {
|
||||
background-size: 12px;
|
||||
}
|
||||
|
||||
#asmtDiv .placeholder {
|
||||
font-style: italic;
|
||||
position: relative;
|
||||
top: 50px;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 8H8V15H0V0H15V8ZM7 8H1V14H7V8ZM7 1H1V7H7V1ZM14 1H8V7H14V1ZM14.9531 12.0234C14.9844 12.1797 15 12.3385 15 12.5C15 12.6615 14.9844 12.8203 14.9531 12.9766L15.9219 13.375L15.5391 14.3047L14.5703 13.8984C14.4818 14.0339 14.3802 14.1589 14.2656 14.2734C14.1562 14.3828 14.0339 14.4818 13.8984 14.5703L14.3047 15.5391L13.375 15.9219L12.9766 14.9531C12.8203 14.9844 12.6615 15 12.5 15C12.3385 15 12.1797 14.9844 12.0234 14.9531L11.625 15.9219L10.6953 15.5391L11.1016 14.5703C10.8307 14.3932 10.6068 14.1693 10.4297 13.8984L9.46094 14.3047L9.07812 13.375L10.0469 12.9766C10.0156 12.8203 10 12.6615 10 12.5C10 12.3385 10.0156 12.1797 10.0469 12.0234L9.07812 11.625L9.46094 10.6953L10.4297 11.1016C10.5182 10.9661 10.6172 10.8438 10.7266 10.7344C10.8411 10.6198 10.9661 10.5182 11.1016 10.4297L10.6953 9.46094L11.625 9.07812L12.0234 10.0469C12.0964 10.0365 12.1667 10.0286 12.2344 10.0234C12.3073 10.013 12.3802 10.0078 12.4531 10.0078C12.5417 10.0078 12.6276 10.013 12.7109 10.0234C12.7943 10.0339 12.8802 10.0469 12.9688 10.0625L13.375 9.07812L14.3047 9.46094L13.8906 10.4453C14.026 10.5339 14.151 10.6328 14.2656 10.7422C14.3802 10.8464 14.4818 10.9661 14.5703 11.1016L15.5391 10.6953L15.9219 11.625L14.9531 12.0234ZM12.5078 14C12.7109 14 12.9036 13.9609 13.0859 13.8828C13.2682 13.7995 13.4271 13.6901 13.5625 13.5547C13.6979 13.4193 13.8047 13.2604 13.8828 13.0781C13.9609 12.8958 14 12.7031 14 12.5C14 12.2917 13.9609 12.0964 13.8828 11.9141C13.8047 11.7318 13.6979 11.5729 13.5625 11.4375C13.4271 11.3021 13.2682 11.1953 13.0859 11.1172C12.9036 11.0391 12.7083 11 12.5 11C12.2969 11 12.1042 11.0391 11.9219 11.1172C11.7396 11.1953 11.5781 11.3047 11.4375 11.4453C11.3021 11.5807 11.1953 11.7396 11.1172 11.9219C11.0391 12.1042 11 12.2969 11 12.5C11 12.7083 11.0391 12.9036 11.1172 13.0859C11.2005 13.2682 11.3099 13.4271 11.4453 13.5625C11.5807 13.6979 11.7396 13.8047 11.9219 13.8828C12.1094 13.9609 12.3047 14 12.5078 14Z" fill="#333333"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 8H8V15H0V0H15V8ZM7 8H1V14H7V8ZM7 1H1V7H7V1ZM14 1H8V7H14V1ZM14.9531 12.0234C14.9844 12.1797 15 12.3385 15 12.5C15 12.6615 14.9844 12.8203 14.9531 12.9766L15.9219 13.375L15.5391 14.3047L14.5703 13.8984C14.4818 14.0339 14.3802 14.1589 14.2656 14.2734C14.1562 14.3828 14.0339 14.4818 13.8984 14.5703L14.3047 15.5391L13.375 15.9219L12.9766 14.9531C12.8203 14.9844 12.6615 15 12.5 15C12.3385 15 12.1797 14.9844 12.0234 14.9531L11.625 15.9219L10.6953 15.5391L11.1016 14.5703C10.8307 14.3932 10.6068 14.1693 10.4297 13.8984L9.46094 14.3047L9.07812 13.375L10.0469 12.9766C10.0156 12.8203 10 12.6615 10 12.5C10 12.3385 10.0156 12.1797 10.0469 12.0234L9.07812 11.625L9.46094 10.6953L10.4297 11.1016C10.5182 10.9661 10.6172 10.8438 10.7266 10.7344C10.8411 10.6198 10.9661 10.5182 11.1016 10.4297L10.6953 9.46094L11.625 9.07812L12.0234 10.0469C12.0964 10.0365 12.1667 10.0286 12.2344 10.0234C12.3073 10.013 12.3802 10.0078 12.4531 10.0078C12.5417 10.0078 12.6276 10.013 12.7109 10.0234C12.7943 10.0339 12.8802 10.0469 12.9688 10.0625L13.375 9.07812L14.3047 9.46094L13.8906 10.4453C14.026 10.5339 14.151 10.6328 14.2656 10.7422C14.3802 10.8464 14.4818 10.9661 14.5703 11.1016L15.5391 10.6953L15.9219 11.625L14.9531 12.0234ZM12.5078 14C12.7109 14 12.9036 13.9609 13.0859 13.8828C13.2682 13.7995 13.4271 13.6901 13.5625 13.5547C13.6979 13.4193 13.8047 13.2604 13.8828 13.0781C13.9609 12.8958 14 12.7031 14 12.5C14 12.2917 13.9609 12.0964 13.8828 11.9141C13.8047 11.7318 13.6979 11.5729 13.5625 11.4375C13.4271 11.3021 13.2682 11.1953 13.0859 11.1172C12.9036 11.0391 12.7083 11 12.5 11C12.2969 11 12.1042 11.0391 11.9219 11.1172C11.7396 11.1953 11.5781 11.3047 11.4375 11.4453C11.3021 11.5807 11.1953 11.7396 11.1172 11.9219C11.0391 12.1042 11 12.2969 11 12.5C11 12.7083 11.0391 12.9036 11.1172 13.0859C11.2005 13.2682 11.3099 13.4271 11.4453 13.5625C11.5807 13.6979 11.7396 13.8047 11.9219 13.8828C12.1094 13.9609 12.3047 14 12.5078 14Z" fill="#ffffff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.asmtview-grid .grid-canvas>.ui-widget-content.slick-row .detailView-toggle {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.asmtview-grid .grid-canvas>.ui-widget-content.slick-row .dynamic-cell-detail {
|
||||
z-index: 101;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
.asmtview-grid .grid-canvas>.ui-widget-content.slick-row .dynamic-cell-detail> :first-child {
|
||||
vertical-align: middle;
|
||||
line-height: 13px;
|
||||
padding: 10px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.asmtview-grid .grid-canvas>.ui-widget-content.slick-row .dynamic-cell-detail>.detail-container {
|
||||
overflow: auto;
|
||||
display: block !important;
|
||||
max-height: 100px !important;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.asmtview-grid .grid-canvas>.ui-widget-content.slick-row .dynamic-cell-detail>.detail-container>div,
|
||||
.asmtview-grid .grid-canvas>.ui-widget-content.slick-row .dynamic-cell-detail>.detail-container>div>span {
|
||||
white-space: normal;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>newquery_16x16</title><path class="cls-1" d="M1.9,4H.38V2.45H1.9Zm0,9.14H.38V11.59H1.9ZM15.63,4H4.19V2.45H15.63Zm0,9.14H4.19V11.59H15.63Zm0-6.09H7.23V5.5h8.39Zm0,3H10.29V8.54h5.34Z"/></svg>
|
||||
|
After Width: | Height: | Size: 338 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1,.cls-2{fill:#fff;}.cls-2{font-size:12px;font-family:FullMDL2Assets, Full MDL2 Assets;}</style></defs><title>newquery_inverse_16x16</title><path class="cls-1" d="M1.79,3.85H.26V2.33H1.79Zm0,9.14H.26V11.47H1.79ZM15.51,3.85H4.08V2.33H15.51Zm0,9.14H4.08V11.47H15.51Zm0-6.09H7.12V5.38h8.39Zm0,3H10.18V8.42h5.34Z"/><text class="cls-2" transform="translate(0.01 11.59)"> </text></svg>
|
||||
|
After Width: | Height: | Size: 490 B |
227
src/sql/workbench/contrib/assessment/common/asmtActions.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IAssessmentService } from 'sql/workbench/services/assessment/common/interfaces';
|
||||
import { SqlAssessmentResult, SqlAssessmentResultItem } from 'azdata';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { AssessmentType, AssessmentTargetType, TARGET_ICON_CLASS } from 'sql/workbench/contrib/assessment/common/consts';
|
||||
import { TelemetryView } from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
|
||||
export interface IAssessmentComponent {
|
||||
showProgress(mode: AssessmentType): any;
|
||||
showInitialResults(result: SqlAssessmentResult, method: AssessmentType): any;
|
||||
appendResults(result: SqlAssessmentResult, method: AssessmentType): any;
|
||||
stopProgress(mode: AssessmentType): any;
|
||||
resultItems: SqlAssessmentResultItem[];
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
|
||||
export class IAsmtActionInfo {
|
||||
ownerUri?: string;
|
||||
component: IAssessmentComponent;
|
||||
connectionId: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
abstract class AsmtServerAction extends Action {
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private asmtType: AssessmentType,
|
||||
@IConnectionManagementService private _connectionManagement: IConnectionManagementService,
|
||||
@ILogService protected _logService: ILogService,
|
||||
@IAdsTelemetryService protected _telemetryService: IAdsTelemetryService
|
||||
) {
|
||||
super(id, label, TARGET_ICON_CLASS[AssessmentTargetType.Server]);
|
||||
}
|
||||
|
||||
public async run(context: IAsmtActionInfo): Promise<boolean> {
|
||||
this._telemetryService.sendActionEvent(TelemetryView.SqlAssessment, this.id);
|
||||
if (context && context.component) {
|
||||
context.component.showProgress(this.asmtType);
|
||||
let serverResults = this.getServerItems(context.ownerUri);
|
||||
let connectionUri: string = this._connectionManagement.getConnectionUriFromId(context.connectionId);
|
||||
let connection = this._connectionManagement.getConnection(connectionUri);
|
||||
let databaseListResult = this._connectionManagement.listDatabases(connectionUri);
|
||||
context.component.showInitialResults(await serverResults, this.asmtType);
|
||||
let dbList = await databaseListResult;
|
||||
if (dbList) {
|
||||
for (let nDbName = 0; nDbName < dbList.databaseNames.length; nDbName++) {
|
||||
if (!context.component.isActive) {
|
||||
break;
|
||||
}
|
||||
let dbName = dbList.databaseNames[nDbName];
|
||||
let newUri = await this._connectionManagement.connectIfNotConnected(connection.cloneWithDatabase(dbName).clone());
|
||||
|
||||
this._logService.info(`Database ${dbName} assessment started`);
|
||||
let dbResult = await this.getDatabaseItems(newUri);
|
||||
this._logService.info(`Database ${dbName} assessment completed`);
|
||||
|
||||
context.component.appendResults(dbResult, this.asmtType);
|
||||
}
|
||||
}
|
||||
|
||||
context.component.stopProgress(this.asmtType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
abstract getServerItems(ownerUri: string): Thenable<SqlAssessmentResult>;
|
||||
abstract getDatabaseItems(ownerUri: string): Thenable<SqlAssessmentResult>;
|
||||
}
|
||||
|
||||
|
||||
export class AsmtServerSelectItemsAction extends AsmtServerAction {
|
||||
public static ID = 'asmtaction.server.getitems';
|
||||
public static LABEL = nls.localize('asmtaction.server.getitems', "View applicable rules");
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService _connectionManagement: IConnectionManagementService,
|
||||
@ILogService _logService: ILogService,
|
||||
@IAssessmentService private _assessmentService: IAssessmentService,
|
||||
@IAdsTelemetryService _telemetryService: IAdsTelemetryService
|
||||
) {
|
||||
super(AsmtServerSelectItemsAction.ID, AsmtServerSelectItemsAction.LABEL,
|
||||
AssessmentType.AvailableRules,
|
||||
_connectionManagement,
|
||||
_logService, _telemetryService);
|
||||
}
|
||||
|
||||
getServerItems(ownerUri: string): Thenable<SqlAssessmentResult> {
|
||||
return this._assessmentService.getAssessmentItems(ownerUri, AssessmentTargetType.Server);
|
||||
}
|
||||
|
||||
getDatabaseItems(ownerUri: string): Thenable<SqlAssessmentResult> {
|
||||
return this._assessmentService.getAssessmentItems(ownerUri, AssessmentTargetType.Database);
|
||||
}
|
||||
}
|
||||
|
||||
export class AsmtDatabaseSelectItemsAction extends Action {
|
||||
public static ID = 'asmtaction.database.getitems';
|
||||
|
||||
constructor(
|
||||
databaseName: string,
|
||||
@IAssessmentService private _assessmentService: IAssessmentService,
|
||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
|
||||
) {
|
||||
super(AsmtDatabaseSelectItemsAction.ID,
|
||||
nls.localize('asmtaction.database.getitems', "View applicable rules for {0}", databaseName),
|
||||
TARGET_ICON_CLASS[AssessmentTargetType.Database]);
|
||||
}
|
||||
|
||||
public async run(context: IAsmtActionInfo): Promise<boolean> {
|
||||
this._telemetryService.sendActionEvent(TelemetryView.SqlAssessment, this.id);
|
||||
if (context && context.component) {
|
||||
context.component.showProgress(AssessmentType.AvailableRules);
|
||||
let dbAsmtResults = await this._assessmentService.getAssessmentItems(context.ownerUri, AssessmentTargetType.Database);
|
||||
context.component.showInitialResults(dbAsmtResults, AssessmentType.AvailableRules);
|
||||
context.component.stopProgress(AssessmentType.AvailableRules);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AsmtServerInvokeItemsAction extends AsmtServerAction {
|
||||
public static ID = 'asmtaction.server.invokeitems';
|
||||
public static LABEL = nls.localize('asmtaction.server.invokeitems', "Invoke Assessment");
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService _connectionManagement: IConnectionManagementService,
|
||||
@ILogService _logService: ILogService,
|
||||
@IAssessmentService private _assessmentService: IAssessmentService,
|
||||
@IAdsTelemetryService _telemetryService: IAdsTelemetryService
|
||||
) {
|
||||
super(AsmtServerInvokeItemsAction.ID, AsmtServerInvokeItemsAction.LABEL, AssessmentType.InvokeAssessment, _connectionManagement, _logService, _telemetryService);
|
||||
}
|
||||
getServerItems(ownerUri: string): Thenable<SqlAssessmentResult> {
|
||||
this._logService.info(`Requesting server items`);
|
||||
return this._assessmentService.assessmentInvoke(ownerUri, AssessmentTargetType.Server);
|
||||
}
|
||||
|
||||
getDatabaseItems(ownerUri: string): Thenable<SqlAssessmentResult> {
|
||||
return this._assessmentService.assessmentInvoke(ownerUri, AssessmentTargetType.Database);
|
||||
}
|
||||
}
|
||||
|
||||
export class AsmtDatabaseInvokeItemsAction extends Action {
|
||||
public static ID = 'asmtaction.database.invokeitems';
|
||||
|
||||
constructor(
|
||||
databaseName: string,
|
||||
@IAssessmentService private _assessmentService: IAssessmentService,
|
||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
|
||||
) {
|
||||
super(AsmtDatabaseInvokeItemsAction.ID,
|
||||
nls.localize('asmtaction.database.invokeitems', "Invoke Assessment for {0}", databaseName),
|
||||
TARGET_ICON_CLASS[AssessmentTargetType.Database]);
|
||||
}
|
||||
|
||||
public async run(context: IAsmtActionInfo): Promise<boolean> {
|
||||
this._telemetryService.sendActionEvent(TelemetryView.SqlAssessment, this.id);
|
||||
if (context && context.component) {
|
||||
context.component.showProgress(AssessmentType.InvokeAssessment);
|
||||
let dbAsmtResults = await this._assessmentService.assessmentInvoke(context.ownerUri, AssessmentTargetType.Database);
|
||||
context.component.showInitialResults(dbAsmtResults, AssessmentType.InvokeAssessment);
|
||||
context.component.stopProgress(AssessmentType.InvokeAssessment);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class AsmtExportAsScriptAction extends Action {
|
||||
public static ID = 'asmtaction.exportasscript';
|
||||
public static LABEL = nls.localize('asmtaction.exportasscript', "Export As Script");
|
||||
|
||||
constructor(
|
||||
@IAssessmentService private _assessmentService: IAssessmentService,
|
||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
|
||||
) {
|
||||
super(AsmtExportAsScriptAction.ID, AsmtExportAsScriptAction.LABEL, 'exportAsScriptIcon');
|
||||
}
|
||||
|
||||
public async run(context: IAsmtActionInfo): Promise<boolean> {
|
||||
this._telemetryService.sendActionEvent(TelemetryView.SqlAssessment, AsmtExportAsScriptAction.ID);
|
||||
if (context && context.component && context.component.resultItems) {
|
||||
await this._assessmentService.generateAssessmentScript(context.ownerUri, context.component.resultItems);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class AsmtSamplesLinkAction extends Action {
|
||||
public static readonly ID = 'asmtaction.showsamples';
|
||||
public static readonly LABEL = nls.localize('asmtaction.showsamples', "View all rules and learn more on GitHub");
|
||||
public static readonly ICON = 'asmt-learnmore';
|
||||
private static readonly configHelpUri = 'https://aka.ms/sql-assessment-api';
|
||||
|
||||
constructor(
|
||||
@IOpenerService private _openerService: IOpenerService,
|
||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
|
||||
|
||||
) {
|
||||
super(AsmtSamplesLinkAction.ID, AsmtSamplesLinkAction.LABEL, AsmtSamplesLinkAction.ICON);
|
||||
}
|
||||
|
||||
public async run(): Promise<boolean> {
|
||||
this._telemetryService.sendActionEvent(TelemetryView.SqlAssessment, AsmtSamplesLinkAction.ID);
|
||||
return this._openerService.open(URI.parse(AsmtSamplesLinkAction.configHelpUri));
|
||||
}
|
||||
}
|
||||
22
src/sql/workbench/contrib/assessment/common/consts.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export enum AssessmentTargetType {
|
||||
Server = 1,
|
||||
Database = 2
|
||||
}
|
||||
|
||||
export enum AssessmentType {
|
||||
AvailableRules = 1,
|
||||
InvokeAssessment = 2
|
||||
}
|
||||
|
||||
export const TARGET_ICON_CLASS: { [targetType: number]: string } = {
|
||||
[AssessmentTargetType.Database]: 'database',
|
||||
[AssessmentTargetType.Server]: 'server-page'
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as azdata from 'azdata';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as assert from 'assert';
|
||||
import { AssessmentType, AssessmentTargetType } from 'sql/workbench/contrib/assessment/common/consts';
|
||||
import {
|
||||
IAssessmentComponent,
|
||||
AsmtServerInvokeItemsAction,
|
||||
AsmtServerSelectItemsAction,
|
||||
AsmtExportAsScriptAction,
|
||||
AsmtSamplesLinkAction,
|
||||
AsmtDatabaseInvokeItemsAction,
|
||||
AsmtDatabaseSelectItemsAction
|
||||
} from 'sql/workbench/contrib/assessment/common/asmtActions';
|
||||
import { AssessmentService } from 'sql/workbench/services/assessment/common/assessmentService';
|
||||
import { NullAdsTelemetryService } from 'sql/platform/telemetry/common/adsTelemetryService';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { OpenerServiceStub } from 'sql/platform/opener/common/openerServiceStub';
|
||||
/**
|
||||
* Class to test Assessment Management Actions
|
||||
*/
|
||||
|
||||
let assessmentResultItems: azdata.SqlAssessmentResultItem[] = [
|
||||
<azdata.SqlAssessmentResultItem>{ checkId: 'check1' },
|
||||
<azdata.SqlAssessmentResultItem>{ checkId: 'check2' },
|
||||
<azdata.SqlAssessmentResultItem>{ checkId: 'check3' }
|
||||
];
|
||||
|
||||
class AssessmentTestViewComponent implements IAssessmentComponent {
|
||||
showProgress(mode: AssessmentType) { return undefined; }
|
||||
showInitialResults(result: azdata.SqlAssessmentResult, method: AssessmentType) { return undefined; }
|
||||
appendResults(result: azdata.SqlAssessmentResult, method: AssessmentType) { }
|
||||
stopProgress(mode: AssessmentType) { return undefined; }
|
||||
resultItems: azdata.SqlAssessmentResultItem[] = assessmentResultItems;
|
||||
isActive: boolean = true;
|
||||
}
|
||||
|
||||
let mockAssessmentService: TypeMoq.Mock<AssessmentService>;
|
||||
let mockAsmtViewComponent: TypeMoq.Mock<IAssessmentComponent>;
|
||||
|
||||
let assessmentResult: azdata.SqlAssessmentResult = {
|
||||
success: true,
|
||||
errorMessage: '',
|
||||
apiVersion: '',
|
||||
items: assessmentResultItems
|
||||
};
|
||||
|
||||
// Tests
|
||||
suite('Assessment Actions', () => {
|
||||
|
||||
// Actions
|
||||
setup(() => {
|
||||
mockAsmtViewComponent = TypeMoq.Mock.ofType<IAssessmentComponent>(AssessmentTestViewComponent);
|
||||
|
||||
mockAssessmentService = TypeMoq.Mock.ofType<AssessmentService>(AssessmentService);
|
||||
mockAssessmentService.setup(s => s.assessmentInvoke(TypeMoq.It.isAny(), AssessmentTargetType.Server)).returns(() => Promise.resolve(assessmentResult));
|
||||
mockAssessmentService.setup(s => s.assessmentInvoke(TypeMoq.It.isAny(), AssessmentTargetType.Database)).returns(() => Promise.resolve(assessmentResult));
|
||||
mockAssessmentService.setup(s => s.getAssessmentItems(TypeMoq.It.isAny(), AssessmentTargetType.Server)).returns(() => Promise.resolve(assessmentResult));
|
||||
mockAssessmentService.setup(s => s.getAssessmentItems(TypeMoq.It.isAny(), AssessmentTargetType.Database)).returns(() => Promise.resolve(assessmentResult));
|
||||
|
||||
let resultStatus: azdata.ResultStatus = {
|
||||
success: true,
|
||||
errorMessage: null
|
||||
};
|
||||
mockAssessmentService.setup(s => s.generateAssessmentScript(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => Promise.resolve(resultStatus));
|
||||
});
|
||||
|
||||
function createConnectionManagementService(dbListResult: azdata.ListDatabasesResult): TypeMoq.Mock<IConnectionManagementService> {
|
||||
let connectionProfile = TypeMoq.Mock.ofType<ConnectionProfile>(ConnectionProfile);
|
||||
connectionProfile.setup(cp => cp.cloneWithDatabase(TypeMoq.It.isAnyString())).returns(() => connectionProfile.object);
|
||||
connectionProfile.setup(cp => cp.clone()).returns(() => connectionProfile.object);
|
||||
let connectionManagementService = TypeMoq.Mock.ofType<IConnectionManagementService>(TestConnectionManagementService);
|
||||
connectionManagementService.setup(c => c.listDatabases(TypeMoq.It.isAny())).returns(() => Promise.resolve(dbListResult));
|
||||
connectionManagementService.setup(c => c.getConnectionUriFromId(TypeMoq.It.isAny())).returns(() => '');
|
||||
connectionManagementService.setup(c => c.getConnection(TypeMoq.It.isAny())).returns(() => connectionProfile.object);
|
||||
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.isAny())).returns(() => Promise.resolve(''));
|
||||
|
||||
return connectionManagementService;
|
||||
}
|
||||
|
||||
test('Get Server Assessment Items Action', async () => {
|
||||
const dbListResult: azdata.ListDatabasesResult = {
|
||||
databaseNames: ['db1', 'db2']
|
||||
};
|
||||
|
||||
const connectionManagementService = createConnectionManagementService(dbListResult);
|
||||
|
||||
const action = new AsmtServerSelectItemsAction(connectionManagementService.object, new NullLogService(), mockAssessmentService.object, new NullAdsTelemetryService());
|
||||
assert.equal(action.id, AsmtServerSelectItemsAction.ID, 'Get Server Rules id action mismatch');
|
||||
assert.equal(action.label, AsmtServerSelectItemsAction.LABEL, 'Get Server Rules label action mismatch');
|
||||
|
||||
let result = await action.run({ ownerUri: '', component: mockAsmtViewComponent.object, connectionId: '' });
|
||||
assert.ok(result, 'Get Server Rules action should succeed');
|
||||
mockAsmtViewComponent.verify(s => s.showProgress(AssessmentType.AvailableRules), TypeMoq.Times.once());
|
||||
mockAssessmentService.verify(s => s.getAssessmentItems(TypeMoq.It.isAny(), AssessmentTargetType.Server), TypeMoq.Times.once());
|
||||
mockAsmtViewComponent.verify(s => s.showInitialResults(TypeMoq.It.isAny(), AssessmentType.AvailableRules), TypeMoq.Times.once());
|
||||
// should be executed for every db in database list
|
||||
mockAssessmentService.verify(s => s.getAssessmentItems(TypeMoq.It.isAny(), AssessmentTargetType.Database), TypeMoq.Times.exactly(dbListResult.databaseNames.length));
|
||||
mockAsmtViewComponent.verify(s => s.appendResults(TypeMoq.It.isAny(), AssessmentType.AvailableRules), TypeMoq.Times.exactly(dbListResult.databaseNames.length));
|
||||
|
||||
mockAsmtViewComponent.verify(s => s.stopProgress(AssessmentType.AvailableRules), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
|
||||
test('Invoke Server Assessment Action', async () => {
|
||||
const dbListResult: azdata.ListDatabasesResult = {
|
||||
databaseNames: ['db1', 'db2']
|
||||
};
|
||||
|
||||
const connectionManagementService = createConnectionManagementService(dbListResult);
|
||||
|
||||
const action = new AsmtServerInvokeItemsAction(connectionManagementService.object, new NullLogService(), mockAssessmentService.object, new NullAdsTelemetryService());
|
||||
assert.equal(action.id, AsmtServerInvokeItemsAction.ID, 'Invoke Server Assessment id action mismatch');
|
||||
assert.equal(action.label, AsmtServerInvokeItemsAction.LABEL, 'Invoke Server Assessment label action mismatch');
|
||||
|
||||
let result = await action.run({ ownerUri: '', component: mockAsmtViewComponent.object, connectionId: '' });
|
||||
assert.ok(result, 'Invoke Server Assessment action should succeed');
|
||||
mockAsmtViewComponent.verify(s => s.showProgress(AssessmentType.InvokeAssessment), TypeMoq.Times.once());
|
||||
mockAssessmentService.verify(s => s.assessmentInvoke(TypeMoq.It.isAny(), AssessmentTargetType.Server), TypeMoq.Times.once());
|
||||
mockAsmtViewComponent.verify(s => s.showInitialResults(TypeMoq.It.isAny(), AssessmentType.InvokeAssessment), TypeMoq.Times.once());
|
||||
// should be executed for every db in database list
|
||||
mockAssessmentService.verify(s => s.assessmentInvoke(TypeMoq.It.isAny(), AssessmentTargetType.Database), TypeMoq.Times.exactly(dbListResult.databaseNames.length));
|
||||
mockAsmtViewComponent.verify(s => s.appendResults(TypeMoq.It.isAny(), AssessmentType.InvokeAssessment), TypeMoq.Times.exactly(dbListResult.databaseNames.length));
|
||||
|
||||
mockAsmtViewComponent.verify(s => s.stopProgress(AssessmentType.InvokeAssessment), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
test('Get Assessment Items Database Action', async () => {
|
||||
const action = new AsmtDatabaseSelectItemsAction('databaseName', mockAssessmentService.object, new NullAdsTelemetryService());
|
||||
assert.equal(action.id, AsmtDatabaseSelectItemsAction.ID, 'Get Database Rules id action mismatch');
|
||||
|
||||
let result = await action.run({ ownerUri: '', component: mockAsmtViewComponent.object, connectionId: '' });
|
||||
assert.ok(result, 'Get Assessment Database action should succeed');
|
||||
mockAsmtViewComponent.verify(s => s.showProgress(AssessmentType.AvailableRules), TypeMoq.Times.once());
|
||||
mockAsmtViewComponent.verify(s => s.showInitialResults(TypeMoq.It.isAny(), AssessmentType.AvailableRules), TypeMoq.Times.once());
|
||||
mockAsmtViewComponent.verify(s => s.stopProgress(AssessmentType.AvailableRules), TypeMoq.Times.once());
|
||||
mockAssessmentService.verify(s => s.getAssessmentItems(TypeMoq.It.isAny(), AssessmentTargetType.Database), TypeMoq.Times.once());
|
||||
|
||||
});
|
||||
|
||||
test('Invoke Database Assessment Action', async () => {
|
||||
const action = new AsmtDatabaseInvokeItemsAction('databaseName', mockAssessmentService.object, new NullAdsTelemetryService());
|
||||
assert.equal(action.id, AsmtDatabaseInvokeItemsAction.ID, 'Invoke Database Assessment id action mismatch');
|
||||
|
||||
let result = await action.run({ ownerUri: '', component: mockAsmtViewComponent.object, connectionId: '' });
|
||||
assert.ok(result, 'Invoke Database Assessment action should succeed');
|
||||
mockAsmtViewComponent.verify(s => s.showProgress(AssessmentType.InvokeAssessment), TypeMoq.Times.once());
|
||||
mockAsmtViewComponent.verify(s => s.showInitialResults(TypeMoq.It.isAny(), AssessmentType.InvokeAssessment), TypeMoq.Times.once());
|
||||
mockAsmtViewComponent.verify(s => s.stopProgress(AssessmentType.InvokeAssessment), TypeMoq.Times.once());
|
||||
mockAssessmentService.verify(s => s.assessmentInvoke(TypeMoq.It.isAny(), AssessmentTargetType.Database), TypeMoq.Times.once());
|
||||
|
||||
});
|
||||
|
||||
test('Generate Script Action', async () => {
|
||||
const action = new AsmtExportAsScriptAction(mockAssessmentService.object, new NullAdsTelemetryService());
|
||||
assert.equal(action.id, AsmtExportAsScriptAction.ID, 'Generate Assessment script id action mismatch');
|
||||
assert.equal(action.label, AsmtExportAsScriptAction.LABEL, 'Generate Assessment script label action mismatch');
|
||||
|
||||
let result = await action.run({ ownerUri: '', component: mockAsmtViewComponent.object, connectionId: '' });
|
||||
assert.ok(result, 'Generate Script action should succeed');
|
||||
mockAssessmentService.verify(s => s.generateAssessmentScript(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
test('Samples Link Action', async () => {
|
||||
let openerService = TypeMoq.Mock.ofType<IOpenerService>(OpenerServiceStub);
|
||||
openerService.setup(s => s.open(TypeMoq.It.isAny())).returns(() => Promise.resolve(true));
|
||||
|
||||
const action = new AsmtSamplesLinkAction(openerService.object, new NullAdsTelemetryService());
|
||||
assert.equal(action.id, AsmtSamplesLinkAction.ID, 'Samples Link id action mismatch');
|
||||
assert.equal(action.label, AsmtSamplesLinkAction.LABEL, 'Samples Link label action mismatch');
|
||||
|
||||
let result = await action.run();
|
||||
assert.ok(result, 'Samples Link action should succeed');
|
||||
openerService.verify(s => s.open(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
});
|
||||
|
||||
});
|
||||
@@ -6,3 +6,4 @@
|
||||
-->
|
||||
|
||||
<agentview-component #agent *ngIf="(controlType) === 'agent'"></agentview-component>
|
||||
<asmtview-component #asmt *ngIf="(controlType) === 'assessment'"></asmtview-component>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser
|
||||
import * as azdata from 'azdata';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { AgentViewComponent } from 'sql/workbench/contrib/jobManagement/browser/agentView.component';
|
||||
import { AsmtViewComponent } from 'sql/workbench/contrib/assessment/browser/asmtView.component';
|
||||
|
||||
@Component({
|
||||
templateUrl: decodeURI(require.toUrl('./controlHostContent.component.html')),
|
||||
@@ -30,6 +31,7 @@ export class ControlHostContent {
|
||||
|
||||
/* Children components */
|
||||
@ViewChild('agent') private _agentViewComponent: AgentViewComponent;
|
||||
@ViewChild('asmt') private _asmtViewComponent: AsmtViewComponent;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface,
|
||||
@@ -38,7 +40,8 @@ export class ControlHostContent {
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
this._agentViewComponent.layout();
|
||||
this._agentViewComponent?.layout();
|
||||
this._asmtViewComponent?.layout();
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
@@ -71,6 +74,8 @@ export class ControlHostContent {
|
||||
}
|
||||
|
||||
public refresh() {
|
||||
this._agentViewComponent.refresh = true;
|
||||
if (this._agentViewComponent !== undefined) {
|
||||
this._agentViewComponent.refresh = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +60,14 @@ import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
|
||||
import { SelectBox } from 'sql/platform/browser/selectBox/selectBox.component';
|
||||
import { InputBox } from 'sql/platform/browser/inputbox/inputBox.component';
|
||||
import { EditableDropDown } from 'sql/platform/browser/editableDropdown/editableDropdown.component';
|
||||
import { AsmtViewComponent } from 'sql/workbench/contrib/assessment/browser/asmtView.component';
|
||||
import { AsmtResultsViewComponent } from 'sql/workbench/contrib/assessment/browser/asmtResultsView.component';
|
||||
|
||||
const baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
|
||||
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
|
||||
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
|
||||
JobsViewComponent, NotebooksViewComponent, AgentViewComponent, JobHistoryComponent, NotebookHistoryComponent, JobStepsViewComponent, AlertsViewComponent, ProxiesViewComponent, OperatorsViewComponent,
|
||||
DashboardModelViewContainer, ModelComponentWrapper, Checkbox, EditableDropDown, SelectBox, InputBox];
|
||||
DashboardModelViewContainer, ModelComponentWrapper, Checkbox, EditableDropDown, SelectBox, InputBox, AsmtViewComponent, AsmtResultsViewComponent];
|
||||
|
||||
/* Panel */
|
||||
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import * as azdata from 'azdata';
|
||||
import { IAssessmentService } from 'sql/workbench/services/assessment/common/interfaces';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
|
||||
export class AssessmentService implements IAssessmentService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private _onDidChange = new Emitter<void>();
|
||||
public readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
private _providers: { [handle: string]: azdata.SqlAssessmentServicesProvider; } = Object.create(null);
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public getAssessmentItems(connectionUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.getAssessmentItems(connectionUri, targetType);
|
||||
});
|
||||
}
|
||||
|
||||
public assessmentInvoke(connectionUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.assessmentInvoke(connectionUri, targetType);
|
||||
});
|
||||
}
|
||||
|
||||
public generateAssessmentScript(connectionUri: string, items: azdata.SqlAssessmentResultItem[]): Thenable<azdata.ResultStatus> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.generateAssessmentScript(items);
|
||||
});
|
||||
}
|
||||
|
||||
public registerProvider(providerId: string, provider: azdata.SqlAssessmentServicesProvider): void {
|
||||
this._providers[providerId] = provider;
|
||||
}
|
||||
|
||||
private _runAction<T>(uri: string, action: (handler: azdata.SqlAssessmentServicesProvider) => Thenable<T>): Thenable<T> {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
|
||||
|
||||
if (!providerId) {
|
||||
return Promise.reject(new Error(localize('asmt.providerIdNotValidError', "Connection is required in order to interact with Assessment Service")));
|
||||
}
|
||||
let handler = this._providers[providerId];
|
||||
if (handler) {
|
||||
return action(handler);
|
||||
} else {
|
||||
return Promise.reject(new Error(localize('asmt.noHandlerRegistered', "No Handler Registered")));
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/sql/workbench/services/assessment/common/interfaces.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const SERVICE_ID = 'assessmentService';
|
||||
|
||||
export const IAssessmentService = createDecorator<IAssessmentService>(SERVICE_ID);
|
||||
|
||||
export interface IAssessmentService {
|
||||
_serviceBrand: undefined;
|
||||
registerProvider(providerId: string, provider: azdata.SqlAssessmentServicesProvider): void;
|
||||
getAssessmentItems(connectionUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult>;
|
||||
assessmentInvoke(connectionUri: string, targetType: number): Thenable<azdata.SqlAssessmentResult>;
|
||||
generateAssessmentScript(connectionUri: string, items: azdata.SqlAssessmentResultItem[]): Thenable<azdata.ResultStatus>;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AssessmentService } from 'sql/workbench/services/assessment/common/assessmentService';
|
||||
import * as assert from 'assert';
|
||||
|
||||
// TESTS ///////////////////////////////////////////////////////////////////
|
||||
suite('Assessment service tests', () => {
|
||||
setup(() => {
|
||||
});
|
||||
|
||||
test('Construction - Assessment service Initialization', () => {
|
||||
let service = new AssessmentService(undefined);
|
||||
assert(service);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -191,6 +191,8 @@ import { DashboardService } from 'sql/platform/dashboard/browser/dashboardServic
|
||||
import { NotebookService } from 'sql/workbench/services/notebook/browser/notebookServiceImpl';
|
||||
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
|
||||
import { IScriptingService, ScriptingService } from 'sql/platform/scripting/common/scriptingService';
|
||||
import { IAssessmentService } from 'sql/workbench/services/assessment/common/interfaces';
|
||||
import { AssessmentService } from 'sql/workbench/services/assessment/common/assessmentService';
|
||||
|
||||
registerSingleton(IDashboardService, DashboardService);
|
||||
registerSingleton(IDashboardViewService, DashboardViewService);
|
||||
@@ -228,6 +230,7 @@ registerSingleton(IQueryEditorService, QueryEditorService);
|
||||
registerSingleton(IAdsTelemetryService, AdsTelemetryService);
|
||||
registerSingleton(IObjectExplorerService, ObjectExplorerService);
|
||||
registerSingleton(IOEShimService, OEShimService);
|
||||
registerSingleton(IAssessmentService, AssessmentService);
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||