diff --git a/src/sql/workbench/browser/modelComponents/loadingSpinner.component.ts b/src/sql/base/browser/ui/loadingSpinner/loadingSpinner.component.ts similarity index 88% rename from src/sql/workbench/browser/modelComponents/loadingSpinner.component.ts rename to src/sql/base/browser/ui/loadingSpinner/loadingSpinner.component.ts index 2c09893a10..bb85018a04 100644 --- a/src/sql/workbench/browser/modelComponents/loadingSpinner.component.ts +++ b/src/sql/base/browser/ui/loadingSpinner/loadingSpinner.component.ts @@ -14,8 +14,8 @@ const DefaultLoadingCompletedMessage = nls.localize('loadingCompletedMessage', " @Component({ selector: 'loading-spinner', template: ` -
-
+
+
` }) diff --git a/src/sql/base/browser/ui/loadingSpinner/loadingSpinner.module.ts b/src/sql/base/browser/ui/loadingSpinner/loadingSpinner.module.ts new file mode 100644 index 0000000000..0c434384e8 --- /dev/null +++ b/src/sql/base/browser/ui/loadingSpinner/loadingSpinner.module.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import LoadingSpinner from './loadingSpinner.component'; + +@NgModule({ + imports: [CommonModule], + exports: [LoadingSpinner], + declarations: [LoadingSpinner] +}) +export class LoadingSpinnerModule { } diff --git a/src/sql/base/browser/ui/loadingSpinner/media/loadingComponent.css b/src/sql/base/browser/ui/loadingSpinner/media/loadingComponent.css new file mode 100644 index 0000000000..e26ffa6be2 --- /dev/null +++ b/src/sql/base/browser/ui/loadingSpinner/media/loadingComponent.css @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +loading-spinner .loading-spinner-container { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} + +loading-spinner .modelview-loadingComponent-status-text { + margin-left: 5px; +} + +loading-spinner .loading-spinner { + height: 20px; + padding-top: 5px; + padding-bottom: 5px; +} + +loading-spinner .modelview-loadingComponent-content-loading { + display: none; +} diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.css b/src/sql/base/browser/ui/propertiesContainer/media/propertiesContainer.css similarity index 64% rename from src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.css rename to src/sql/base/browser/ui/propertiesContainer/media/propertiesContainer.css index c3136b1501..a5b291a5ef 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.css +++ b/src/sql/base/browser/ui/propertiesContainer/media/propertiesContainer.css @@ -3,30 +3,30 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -properties-widget .twoColumns.grid-container { +properties-container .twoColumns.grid-container { grid-template-columns: 50% 50%; } -properties-widget .oneColumn.grid-container { +properties-container .oneColumn.grid-container { grid-template-columns: 100%; } -properties-widget .columnLayout.property { +properties-container .columnLayout.property { flex-direction: column; } -properties-widget .propertyName { +properties-container .propertyName { opacity: 0.6; font-size: 12px; flex: 0 0 auto } -.vs-dark properties-widget .propertyName, -.hc-black properties-widget .propertyName { +.vs-dark properties-container .propertyName, +.hc-black properties-container .propertyName { opacity: 1; } -properties-widget .propertyValue { +properties-container .propertyValue { font-size: 12px; flex: 1 1 auto; margin-right: 5px; @@ -35,11 +35,11 @@ properties-widget .propertyValue { text-overflow: ellipsis; } -properties-widget .splitter { +properties-container .splitter { flex: 0 0 15px; text-align: center; } -properties-widget .columnLayout .splitter { +properties-container .columnLayout .splitter { display: none; } diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.html b/src/sql/base/browser/ui/propertiesContainer/propertiesContainer.component.html similarity index 78% rename from src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.html rename to src/sql/base/browser/ui/propertiesContainer/propertiesContainer.component.html index 02f9f8185a..9c7198743a 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.html +++ b/src/sql/base/browser/ui/propertiesContainer/propertiesContainer.component.html @@ -4,10 +4,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ --> - +
- +
{{item.displayName}}
:
diff --git a/src/sql/base/browser/ui/propertiesContainer/propertiesContainer.component.ts b/src/sql/base/browser/ui/propertiesContainer/propertiesContainer.component.ts new file mode 100644 index 0000000000..9ab169dbf1 --- /dev/null +++ b/src/sql/base/browser/ui/propertiesContainer/propertiesContainer.component.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * 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/propertiesContainer'; + +import { Component, Inject, forwardRef, ChangeDetectorRef, OnInit, ElementRef } from '@angular/core'; +import { EventType, addDisposableListener } from 'vs/base/browser/dom'; +import * as nls from 'vs/nls'; +import { Disposable } from 'vs/base/common/lifecycle'; + +enum GridDisplayLayout { + twoColumns = 'twoColumns', + oneColumn = 'oneColumn' +} + +enum PropertyLayoutDirection { + row = 'rowLayout', + column = 'columnLayout' +} + +export interface DisplayProperty { + displayName: string; + value: string; +} + +const collapseHeight = 25; +const horizontalPropertyHeight = 28; +const verticalPropertyHeight = 46; + +@Component({ + selector: 'properties-container', + templateUrl: decodeURI(require.toUrl('./propertiesContainer.component.html')) +}) +export class PropertiesContainer extends Disposable implements OnInit { + public gridDisplayLayout = GridDisplayLayout.twoColumns; + public propertyLayout = PropertyLayoutDirection.row; + public loadingMessage: string = nls.localize('loadingProperties', "Loading properties"); + public loadingCompletedMessage: string = nls.localize('loadingPropertiesCompleted', "Loading properties completed"); + public height: number; + + private _loading: boolean = true; + private _displayProperties: DisplayProperty[] = []; + + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, + @Inject(forwardRef(() => ElementRef)) el: ElementRef + ) { + super(); + } + + ngOnInit() { + this._register(addDisposableListener(window, EventType.RESIZE, () => this.layoutDisplayProperties())); + this._changeRef.detectChanges(); + } + + private layoutDisplayProperties(): void { + // Reflow: + // 2 columns w/ horizontal alignment : 1366px and above + // 2 columns w/ vertical alignment : 1024 - 1365px + // 1 column w/ vertical alignment : 1024px or less + if (!this.loading) { + if (window.innerWidth >= 1366) { + this.gridDisplayLayout = GridDisplayLayout.twoColumns; + this.propertyLayout = PropertyLayoutDirection.row; + this.height = Math.ceil(this.displayProperties.length / 2) * horizontalPropertyHeight + collapseHeight; + } else if (window.innerWidth < 1366 && window.innerWidth >= 1024) { + this.gridDisplayLayout = GridDisplayLayout.twoColumns; + this.propertyLayout = PropertyLayoutDirection.column; + this.height = Math.ceil(this.displayProperties.length / 2) * verticalPropertyHeight + collapseHeight; + } else if (window.innerWidth < 1024) { + this.gridDisplayLayout = GridDisplayLayout.oneColumn; + this.propertyLayout = PropertyLayoutDirection.column; + this.height = this.displayProperties.length * verticalPropertyHeight + collapseHeight; + } + + this._changeRef.detectChanges(); + } + } + + public set displayProperties(displayProperties: DisplayProperty[]) { + this._displayProperties = displayProperties; + this.layoutDisplayProperties(); + } + + public get displayProperties(): DisplayProperty[] { + return this._displayProperties; + } + + public set loading(loading: boolean) { + this._loading = loading; + this._changeRef.detectChanges(); + } + + public get loading(): boolean { + return this._loading; + } +} diff --git a/src/sql/base/browser/ui/propertiesContainer/propertiesContainer.module.ts b/src/sql/base/browser/ui/propertiesContainer/propertiesContainer.module.ts new file mode 100644 index 0000000000..086f4518be --- /dev/null +++ b/src/sql/base/browser/ui/propertiesContainer/propertiesContainer.module.ts @@ -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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { PropertiesContainer } from './propertiesContainer.component'; +import { LoadingSpinnerModule } from 'sql/base/browser/ui/loadingSpinner/loadingSpinner.module'; + +@NgModule({ + imports: [CommonModule, LoadingSpinnerModule], + exports: [PropertiesContainer], + declarations: [PropertiesContainer] +}) +export class PropertiesContainerModule { } diff --git a/src/sql/workbench/contrib/dashboard/browser/dashboard.module.ts b/src/sql/workbench/contrib/dashboard/browser/dashboard.module.ts index bc70d305f4..8ef8d64aaf 100644 --- a/src/sql/workbench/contrib/dashboard/browser/dashboard.module.ts +++ b/src/sql/workbench/contrib/dashboard/browser/dashboard.module.ts @@ -56,7 +56,6 @@ import { OperatorsViewComponent } from 'sql/workbench/contrib/jobManagement/brow import { ProxiesViewComponent } from 'sql/workbench/contrib/jobManagement/browser/proxiesView.component'; import { NotebooksViewComponent } from 'sql/workbench/contrib/jobManagement/browser/notebooksView.component'; import { NotebookHistoryComponent } from 'sql/workbench/contrib/jobManagement/browser/notebookHistory.component'; -import LoadingSpinner from 'sql/workbench/browser/modelComponents/loadingSpinner.component'; 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'; @@ -66,7 +65,7 @@ const baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWid 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, LoadingSpinner]; + DashboardModelViewContainer, ModelComponentWrapper, Checkbox, EditableDropDown, SelectBox, InputBox]; /* Panel */ import { PanelModule } from 'sql/base/browser/ui/panel/panel.module'; @@ -87,6 +86,8 @@ import { WebviewWidget } from 'sql/workbench/contrib/dashboard/browser/widgets/w import { JobStepsViewComponent } from 'sql/workbench/contrib/jobManagement/browser/jobStepsView.component'; import { IInstantiationService, _util } from 'vs/platform/instantiation/common/instantiation'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; +import { PropertiesContainerModule } from 'sql/base/browser/ui/propertiesContainer/propertiesContainer.module'; +import { LoadingSpinnerModule } from 'sql/base/browser/ui/loadingSpinner/loadingSpinner.module'; const widgetComponents = [ @@ -140,7 +141,9 @@ export const DashboardModule = (params, selector: string, instantiationService: ChartsModule, RouterModule.forRoot(appRoutes), PanelModule, - ScrollableModule + ScrollableModule, + PropertiesContainerModule, + LoadingSpinnerModule ], providers: [ { provide: APP_BASE_HREF, useValue: '/' }, diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.ts index b4c5701576..544fb0192b 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.ts @@ -2,9 +2,7 @@ * 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!./propertiesWidget'; - -import { Component, Inject, forwardRef, ChangeDetectorRef, OnInit, ElementRef } from '@angular/core'; +import { Component, Inject, forwardRef, ChangeDetectorRef, OnInit, ElementRef, ViewChild } from '@angular/core'; import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget'; import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service'; @@ -12,13 +10,13 @@ import { ConnectionManagementInfo } from 'sql/platform/connection/common/connect import { IDashboardRegistry, Extensions as DashboardExtensions } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry'; import { DatabaseInfo, ServerInfo } from 'azdata'; - -import { EventType, addDisposableListener } from 'vs/base/browser/dom'; import * as types from 'vs/base/common/types'; import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { subscriptionToDisposable } from 'sql/base/browser/lifecycle'; +import { PropertiesContainer, DisplayProperty } from 'sql/base/browser/ui/propertiesContainer/propertiesContainer.component'; +import { convertSizeToNumber } from 'sql/base/browser/dom'; export interface PropertiesConfig { properties: Array; @@ -52,37 +50,13 @@ export interface Property { const dashboardRegistry = Registry.as(DashboardExtensions.DashboardContributions); -export interface DisplayProperty { - displayName: string; - value: string; -} - -enum GridDisplayLayout { - twoColumns = 'twoColumns', - oneColumn = 'oneColumn' -} - -enum PropertyLayoutDirection { - row = 'rowLayout', - column = 'columnLayout' -} - - -const collapseHeight = 25; -const horizontalPropertyHeight = 28; -const verticalPropertyHeight = 46; - @Component({ selector: 'properties-widget', - templateUrl: decodeURI(require.toUrl('./propertiesWidget.component.html')) + template: '' }) export class PropertiesWidgetComponent extends DashboardWidget implements IDashboardWidget, OnInit { + @ViewChild(PropertiesContainer) private _propertiesContainer: PropertiesContainer; private _connection: ConnectionManagementInfo; - private _databaseInfo: DatabaseInfo; - private _properties: Array; - public gridDisplayLayout: GridDisplayLayout; - public propertyLayout: PropertyLayoutDirection; - public height: number; constructor( @Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface, @@ -92,14 +66,11 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb @Inject(ILogService) private logService: ILogService ) { super(changeRef); - this._loadingMessage = nls.localize('loadingProperties', "Loading properties"); - this._loadingCompletedMessage = nls.localize('loadingPropertiesCompleted', "Loading properties completed"); this.init(); } ngOnInit() { this._inited = true; - this._register(addDisposableListener(window, EventType.RESIZE, () => this.layoutProperties())); this._changeRef.detectChanges(); } @@ -110,43 +81,22 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb private init(): void { this._connection = this._bootstrap.connectionManagementService.connectionInfo; this.setLoadingStatus(true); - this._register(subscriptionToDisposable(this._bootstrap.adminService.databaseInfo.subscribe(data => { - this._databaseInfo = data; - this._changeRef.detectChanges(); - this.parseProperties(); + this._register(subscriptionToDisposable(this._bootstrap.adminService.databaseInfo.subscribe(databaseInfo => { + const displayProperties = this.parseProperties(databaseInfo); + if (this._inited) { + this._propertiesContainer.displayProperties = displayProperties; + this._changeRef.detectChanges(); + } else { + this.logService.info('Database properties successfully retrieved but component not initialized yet'); + } this.setLoadingStatus(false); - this.layoutProperties(); }, error => { this.setLoadingStatus(false); (this._el.nativeElement).innerText = nls.localize('dashboard.properties.error', "Unable to load dashboard properties"); }))); } - private layoutProperties(): void { - // Reflow: - // 2 columns w/ horizontal alignment : 1366px and above - // 2 columns w/ vertical alignment : 1024 - 1365px - // 1 column w/ vertical alignment : 1024px or less - if (!this._loading) { - if (window.innerWidth >= 1366) { - this.gridDisplayLayout = GridDisplayLayout.twoColumns; - this.propertyLayout = PropertyLayoutDirection.row; - this.height = Math.ceil(this._properties.length / 2) * horizontalPropertyHeight + collapseHeight; - } else if (window.innerWidth < 1366 && window.innerWidth >= 1024) { - this.gridDisplayLayout = GridDisplayLayout.twoColumns; - this.propertyLayout = PropertyLayoutDirection.column; - this.height = Math.ceil(this._properties.length / 2) * verticalPropertyHeight + collapseHeight; - } else if (window.innerWidth < 1024) { - this.gridDisplayLayout = GridDisplayLayout.oneColumn; - this.propertyLayout = PropertyLayoutDirection.column; - this.height = this._properties.length * verticalPropertyHeight + collapseHeight; - } - - this._changeRef.detectChanges(); - } - } - - private parseProperties() { + private parseProperties(databaseInfo?: DatabaseInfo): DisplayProperty[] { const provider = this._config.provider; let propertyArray: Array; @@ -160,7 +110,7 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb if (!providerProperties) { this.logService.error('No property definitions found for provider', provider); - return; + return []; } let flavor: FlavorProperties; @@ -171,7 +121,7 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb } else if (providerProperties.flavors.length === 0) { this.logService.error('No flavor definitions found for "', provider, '. If there are not multiple flavors of this provider, add one flavor without a condition'); - return; + return []; } else { const flavorArray = providerProperties.flavors.filter((item) => { @@ -196,10 +146,10 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb if (flavorArray.length === 0) { this.logService.error('Could not determine flavor'); - return; + return []; } else if (flavorArray.length > 1) { this.logService.error('Multiple flavors matched correctly for this provider', provider); - return; + return []; } flavor = flavorArray[0]; @@ -228,14 +178,14 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb let infoObject: ServerInfo | {}; if (this._config.context === 'database') { - if (this._databaseInfo && this._databaseInfo.options) { - infoObject = this._databaseInfo.options; + if (databaseInfo?.options) { + infoObject = databaseInfo.options; } } else { infoObject = this._connection.serverInfo; } - this._properties = propertyArray.map(property => { + return propertyArray.map(property => { let propertyObject = this.getValueOrDefault(infoObject, property.value, property.default || '--'); // make sure the value we got shouldn't be ignored @@ -294,4 +244,15 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb } return val; } + + protected setLoadingStatus(loading: boolean): void { + super.setLoadingStatus(loading); + if (this._inited) { + this._propertiesContainer.loading = loading; + } + } + + public get height(): number { + return convertSizeToNumber(this._propertiesContainer.height); + } } diff --git a/src/sql/workbench/contrib/dashboard/test/electron-browser/propertiesWidget.component.test.ts b/src/sql/workbench/contrib/dashboard/test/electron-browser/propertiesWidget.component.test.ts index 6a8a255043..9e88594e47 100644 --- a/src/sql/workbench/contrib/dashboard/test/electron-browser/propertiesWidget.component.test.ts +++ b/src/sql/workbench/contrib/dashboard/test/electron-browser/propertiesWidget.component.test.ts @@ -16,6 +16,7 @@ import * as TypeMoq from 'typemoq'; import * as assert from 'assert'; import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { NullLogService } from 'vs/platform/log/common/log'; +import { DisplayProperty } from 'sql/base/browser/ui/propertiesContainer/propertiesContainer.component'; class TestChangeDetectorRef extends ChangeDetectorRef { reattach(): void { @@ -103,10 +104,10 @@ suite('Dashboard Properties Widget Tests', () => { return new Promise(resolve => { // because config parsing is done async we need to put our asserts on the thread stack setImmediate(() => { - // because properties is private we need to do some work arounds to access it. - assert.equal((testComponent)._properties.length, 1); - assert.equal((testComponent)._properties[0].displayName, 'Test'); - assert.equal((testComponent)._properties[0].value, 'Test Property'); + const displayProperties: DisplayProperty[] = (testComponent as any).parseProperties(databaseInfo); + assert.equal(displayProperties.length, 1); + assert.equal(displayProperties[0].displayName, 'Test'); + assert.equal(displayProperties[0].value, 'Test Property'); resolve(); }); }); diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.module.ts b/src/sql/workbench/contrib/notebook/browser/notebook.module.ts index 586ffb575e..ae0ad455b4 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.module.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.module.ts @@ -20,7 +20,7 @@ import { OutputAreaComponent } from 'sql/workbench/contrib/notebook/browser/cell import { OutputComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/output.component'; import { StdInComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/stdin.component'; import { PlaceholderCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/placeholderCell.component'; -import LoadingSpinner from 'sql/workbench/browser/modelComponents/loadingSpinner.component'; +import LoadingSpinner from 'sql/base/browser/ui/loadingSpinner/loadingSpinner.component'; 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';