diff --git a/extensions/sql-assessment/resources/dark/status_info.svg b/extensions/sql-assessment/resources/dark/status_info.svg new file mode 100644 index 0000000000..c998075774 --- /dev/null +++ b/extensions/sql-assessment/resources/dark/status_info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/sql-assessment/resources/light/status_info.svg b/extensions/sql-assessment/resources/light/status_info.svg new file mode 100644 index 0000000000..b1c0cbd521 --- /dev/null +++ b/extensions/sql-assessment/resources/light/status_info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/sql-assessment/src/assessmentResultGrid.ts b/extensions/sql-assessment/src/assessmentResultGrid.ts index b3a4a6eac2..3676a5949d 100644 --- a/extensions/sql-assessment/src/assessmentResultGrid.ts +++ b/extensions/sql-assessment/src/assessmentResultGrid.ts @@ -30,7 +30,6 @@ export class AssessmentResultGrid implements vscode.Disposable { private asmtType!: AssessmentType; private targetTypeIcon: { [targetType: number]: azdata.IconColumnCellValue }; - private readonly checkIdColOrder = 5; private readonly targetColOrder = 1; private readonly messageColOrder = 3; @@ -81,7 +80,6 @@ export class AssessmentResultGrid implements vscode.Disposable { headerFilter: true }).component(); - this.toDispose.push( this.table.onRowSelected(async () => { if (this.table.selectedRows?.length !== 1) { @@ -90,7 +88,6 @@ export class AssessmentResultGrid implements vscode.Disposable { await this.showDetails(this.table.selectedRows[0]); })); - this.rootContainer = view.modelBuilder.flexContainer() .withItems([this.table], { flex: '1 1 auto', @@ -109,7 +106,6 @@ export class AssessmentResultGrid implements vscode.Disposable { flex: '0 0 200px', order: 2, CSSStyles: { - 'padding-bottom': '15px', 'visibility': 'hidden' } }); @@ -132,17 +128,21 @@ export class AssessmentResultGrid implements vscode.Disposable { this.rootContainer.setItemLayout(this.table, { flex: '1 1 auto', CSSStyles: { - 'height': '100%' + 'height': '100%', + 'border-bottom': '3px solid rgb(221, 221, 221)' } }); await this.table.updateProperties({ 'height': '100%' }); - - this.detailsPanel.updateCssStyles({ - 'visibility': 'hidden' - }); + if (this.dataItems.length > 0) { + this.table.selectedRows = [0]; + } else { + await this.detailsPanel.updateCssStyles({ + 'visibility': 'hidden' + }); + } } // we need to filter out warnings and error results since we don't have an appropriate way of displaying such messages. @@ -196,8 +196,6 @@ export class AssessmentResultGrid implements vscode.Disposable { }); } - - private createDetailsPanel(view: azdata.ModelView): azdata.FlexContainer { const root = view.modelBuilder.flexContainer() @@ -206,8 +204,7 @@ export class AssessmentResultGrid implements vscode.Disposable { height: '200px', }).withProperties({ CSSStyles: { - 'padding': '20px', - 'border-top': '3px solid rgb(221, 221, 221)' + 'padding': '0px 10px' } }).component(); const cssNoMarginFloatLeft = { 'margin': '0px', 'float': 'left' }; @@ -285,6 +282,7 @@ export class AssessmentResultGrid implements vscode.Disposable { return root; } + private clearOutDefaultRuleset(tags: string[]): string[] { let idx = tags.findIndex(item => item.toUpperCase() === 'DEFAULTRULESET'); if (idx > -1) { diff --git a/extensions/sql-assessment/src/tabs/assessmentMainTab.ts b/extensions/sql-assessment/src/tabs/assessmentMainTab.ts index f3af36d64e..8b3e42acff 100644 --- a/extensions/sql-assessment/src/tabs/assessmentMainTab.ts +++ b/extensions/sql-assessment/src/tabs/assessmentMainTab.ts @@ -14,13 +14,15 @@ import { HTMLReportBuilder } from '../htmlReportGenerator'; import { AssessmentResultGrid } from '../assessmentResultGrid'; import { LocalizedStrings } from '../localized'; import { TelemetryReporter, SqlAssessmentTelemetryView, SqlTelemetryActions } from '../telemetry'; +import { EOL } from 'os'; const localize = nls.loadMessageBundle(); export class SqlAssessmentMainTab extends SqlAssessmentTab { - private assessmentPropertiesContainer!: azdata.PropertiesContainerComponent; private apiVersionPropItem: azdata.PropertiesContainerItem; private defaultRulesetPropItem: azdata.PropertiesContainerItem; + private serverProps: azdata.PropertiesContainerItem[] = []; + private invokeAssessmentLabel: string = localize('invokeAssessmentLabelServer', "Invoke assessment"); private getItemsLabel: string = localize('getAssessmentItemsServer', "View applicable rules"); private btnExportAsScript!: azdata.ButtonComponent; @@ -31,7 +33,6 @@ export class SqlAssessmentMainTab extends SqlAssessmentTab { private resultGrid!: AssessmentResultGrid; - public constructor(extensionContext: vscode.ExtensionContext, engine: AssessmentEngine) { super(extensionContext, LocalizedStrings.ASSESSMENT_TAB_NAME, 'MainTab', { dark: extensionContext.asAbsolutePath('resources/dark/server.svg'), @@ -64,12 +65,11 @@ export class SqlAssessmentMainTab extends SqlAssessmentTab { height: '100%', }).component(); + await this.createServerProperties(view); - rootContainer.addItem(await this.createPropertiesSection(view), { flex: '0 0 auto' }); rootContainer.addItem(await this.createToolbar(view), { flex: '0 0 auto', CSSStyles: { 'border-top': '3px solid rgb(221, 221, 221)', - 'margin-top': '20px', 'height': '32px' } }); @@ -78,74 +78,22 @@ export class SqlAssessmentMainTab extends SqlAssessmentTab { rootContainer.addItem(this.resultGrid.component, { flex: '1 1 auto', CSSStyles: { - 'padding-bottom': '15px' + 'padding-bottom': '10px' } }); return rootContainer; } - private async createPropertiesSection(view: azdata.ModelView): Promise { + private async createServerProperties(view: azdata.ModelView): Promise { const serverInfo = await azdata.connection.getServerInfo(view.connection.connectionId); const connectionProfile = await azdata.connection.getCurrentConnection(); - - const propertiesContainer = view.modelBuilder.flexContainer() - .withLayout({ - flexFlow: 'row', - justifyContent: 'flex-start' - }).component(); - - const apiInformationContainer = view.modelBuilder.flexContainer() - .withLayout({ - flexFlow: 'column', - alignContent: 'flex-start' - }).component(); - apiInformationContainer.addItem( - view.modelBuilder.text().withProperties({ value: LocalizedStrings.SECTION_TITLE_API }).component(), { - CSSStyles: { 'font-size': 'larger' } - }); - - this.assessmentPropertiesContainer = view.modelBuilder.propertiesContainer() - .withProperties({ - propertyItems: [ - this.apiVersionPropItem, - this.defaultRulesetPropItem] - }).component(); - - apiInformationContainer.addItem(this.assessmentPropertiesContainer, { - CSSStyles: { - 'margin-left': '20px' - } - }); - - const sqlServerContainer = view.modelBuilder.flexContainer() - .withLayout({ - flexFlow: 'column', - alignContent: 'flex-start' - }).component(); - sqlServerContainer.addItem( - view.modelBuilder.text().withProperties({ value: LocalizedStrings.SECTION_TITLE_SQL_SERVER }).component(), { - CSSStyles: { 'font-size': 'larger' } - }); - sqlServerContainer.addItem( - view.modelBuilder.propertiesContainer() - .withProperties({ - propertyItems: [ - { displayName: LocalizedStrings.SERVER_VERSION, value: serverInfo.serverVersion }, - { displayName: LocalizedStrings.SERVER_INSTANCENAME, value: connectionProfile.serverName }, - { displayName: LocalizedStrings.SERVER_EDITION, value: serverInfo.serverEdition }, - { displayName: LocalizedStrings.SERVER_OSVERSION, value: serverInfo.osVersion }, - ] - }).component(), { - CSSStyles: { - 'margin-left': '20px' - } - }); - - propertiesContainer.addItem(apiInformationContainer, { flex: '0 0 300px', CSSStyles: { 'margin-left': '10px' } }); - propertiesContainer.addItem(sqlServerContainer, { flex: '1 1 auto' }); - - return propertiesContainer; + this.serverProps = [ + { displayName: LocalizedStrings.SERVER_VERSION, value: serverInfo.serverVersion }, + { displayName: LocalizedStrings.SERVER_INSTANCENAME, value: connectionProfile.serverName }, + { displayName: LocalizedStrings.SERVER_EDITION, value: serverInfo.serverEdition }, + { displayName: LocalizedStrings.SERVER_OSVERSION, value: serverInfo.osVersion }, + ]; } private async createToolbar(view: azdata.ModelView): Promise { @@ -279,6 +227,42 @@ export class SqlAssessmentMainTab extends SqlAssessmentTab { vscode.env.openExternal(vscode.Uri.parse('https://aka.ms/sql-assessment-api')); })); + let btnAPIDetails = view.modelBuilder.button() + .withProperties({ + label: LocalizedStrings.SECTION_TITLE_API, + iconPath: { + dark: this.extensionContext.asAbsolutePath('resources/dark/status_info.svg'), + light: this.extensionContext.asAbsolutePath('resources/light/status_info.svg') + }, + }).component(); + this.toDispose.push(btnAPIDetails.onDidClick(async () => { + let infoArray: azdata.PropertiesContainerItem[] = []; + + if (this.apiVersionPropItem.value) { + infoArray.push(this.apiVersionPropItem); + } + + if (this.defaultRulesetPropItem.value) { + infoArray.push(this.defaultRulesetPropItem); + } + + infoArray.push(...this.serverProps); + const message = localize('msgBoxAsmtInfo', "SQL Assessment Information") + EOL + EOL + + infoArray.map(v => `${v.displayName}: ${v.value}`).join(EOL); + + const copy: vscode.MessageItem = { title: localize('msgBoxCopyBtn', "Copy") }; + const ok: vscode.MessageItem = { isCloseAffordance: true, title: localize('ok', "OK") }; + + const response = await vscode.window.showInformationMessage(message, { modal: true }, copy, ok); + if (response === copy) { + await vscode.env.clipboard.writeText(message); + vscode.window.showInformationMessage(localize('msgBoxCopied', 'SQL Assessment Information copied')); + } + })); + + btnGetAssessmentItemsLoading.loading = false; + btnInvokeAssessmentLoading.loading = false; + return view.modelBuilder.toolbarContainer() .withToolbarItems( [ @@ -286,7 +270,8 @@ export class SqlAssessmentMainTab extends SqlAssessmentTab { { component: btnGetAssessmentItemsLoading }, { component: this.btnExportAsScript }, { component: this.btnHTMLExport }, - { component: btnViewSamples } + { component: btnViewSamples }, + { component: btnAPIDetails } ] ).component(); } @@ -294,10 +279,6 @@ export class SqlAssessmentMainTab extends SqlAssessmentTab { private displayResults(result: azdata.SqlAssessmentResult, assessmentType: AssessmentType): void { this.apiVersionPropItem.value = result.apiVersion; this.defaultRulesetPropItem.value = result.items?.length > 0 ? result.items[0].rulesetVersion : ''; - this.assessmentPropertiesContainer.propertyItems = [ - this.apiVersionPropItem, - this.defaultRulesetPropItem - ]; this.resultGrid.displayResult(result, assessmentType); this.btnExportAsScript.enabled = this.btnHTMLExport.enabled = assessmentType === AssessmentType.InvokeAssessment; diff --git a/extensions/sql-assessment/src/tabs/historyTab.ts b/extensions/sql-assessment/src/tabs/historyTab.ts index 356de7e12e..a7e1ec4c03 100644 --- a/extensions/sql-assessment/src/tabs/historyTab.ts +++ b/extensions/sql-assessment/src/tabs/historyTab.ts @@ -88,7 +88,11 @@ export class SqlAssessmentHistoryTab extends SqlAssessmentTab { this.resultGrid.displayResult(historyResult.result, AssessmentType.InvokeAssessment); title.value = localize('asmt.history.resultsTitle', "Assessment Results from {0}", new Date(historyResult.dateUpdated).toLocaleString()); root.addItem(infoPanel, { flex: `0 0 50px` }); - root.addItem(this.resultGrid.component); + root.addItem(this.resultGrid.component, { + flex: '1 1 auto', CSSStyles: { + 'padding-bottom': '10px' + } + }); this.summaryTable.selectedRows = []; }