Move properties container and loading spinner to common components (#10058)

* Move properties container and loading spinner to common components

* Fix compile error

* Fix tests
This commit is contained in:
Charles Gagnon
2020-04-21 09:36:47 -07:00
committed by GitHub
parent a34feb4448
commit a4ae2ca65f
11 changed files with 214 additions and 93 deletions

View File

@@ -1,47 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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/loadingComponent';
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import * as nls from 'vs/nls';
import { status } from 'vs/base/browser/ui/aria/aria';
const DefaultLoadingMessage = nls.localize('loadingMessage', "Loading");
const DefaultLoadingCompletedMessage = nls.localize('loadingCompletedMessage', "Loading completed");
@Component({
selector: 'loading-spinner',
template: `
<div class="modelview-loadingComponent-container" *ngIf="loading">
<div class="modelview-loadingComponent-spinner" *ngIf="loading" [title]="_loadingMessage" #spinnerElement></div>
</div>
`
})
export default class LoadingSpinner implements OnChanges {
ngOnChanges(changes: SimpleChanges): void {
if (changes.loading !== undefined) {
const message = this.loading ? this._loadingMessage : this._loadingCompletedMessage;
status(message);
}
}
get _loadingMessage(): string {
return this.loadingMessage ? this.loadingMessage : DefaultLoadingMessage;
}
get _loadingCompletedMessage(): string {
return this.loadingCompletedMessage ? this.loadingCompletedMessage : DefaultLoadingCompletedMessage;
}
@Input()
loading: boolean;
@Input()
loadingMessage: string;
@Input()
loadingCompletedMessage: string;
}

View File

@@ -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: '/' },

View File

@@ -1,17 +0,0 @@
<!--
/*---------------------------------------------------------------------------------------------
* 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]="_loading" [loadingMessage]="_loadingMessage"
[loadingCompletedMessage]="_loadingCompletedMessage"></loading-spinner>
<div class="grid-container {{gridDisplayLayout}}" style="position: absolute; height: 100%; width: 100%;" [style.display]="_loading ? 'none':'grid'">
<ng-template ngFor let-item [ngForOf]="_properties">
<div class="property {{propertyLayout}}" style="display:flex">
<div class="propertyName">{{item.displayName}}</div>
<div class="splitter">:</div>
<div class="propertyValue">{{item.value}}</div>
</div>
</ng-template>
</div>

View File

@@ -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<Property>;
@@ -52,37 +50,13 @@ export interface Property {
const dashboardRegistry = Registry.as<IDashboardRegistry>(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: '<properties-container></properties-container>'
})
export class PropertiesWidgetComponent extends DashboardWidget implements IDashboardWidget, OnInit {
@ViewChild(PropertiesContainer) private _propertiesContainer: PropertiesContainer;
private _connection: ConnectionManagementInfo;
private _databaseInfo: DatabaseInfo;
private _properties: Array<DisplayProperty>;
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);
(<HTMLElement>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<Property>;
@@ -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<string>(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);
}
}

View File

@@ -1,45 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
properties-widget .twoColumns.grid-container {
grid-template-columns: 50% 50%;
}
properties-widget .oneColumn.grid-container {
grid-template-columns: 100%;
}
properties-widget .columnLayout.property {
flex-direction: column;
}
properties-widget .propertyName {
opacity: 0.6;
font-size: 12px;
flex: 0 0 auto
}
.vs-dark properties-widget .propertyName,
.hc-black properties-widget .propertyName {
opacity: 1;
}
properties-widget .propertyValue {
font-size: 12px;
flex: 1 1 auto;
margin-right: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
properties-widget .splitter {
flex: 0 0 15px;
text-align: center;
}
properties-widget .columnLayout .splitter {
display: none;
}

View File

@@ -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((<any>testComponent)._properties.length, 1);
assert.equal((<any>testComponent)._properties[0].displayName, 'Test');
assert.equal((<any>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();
});
});

View File

@@ -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';