Browse Source

Fix timeseries widget (invoke data updated callback from data aggregator on initial data). Improve widget selector.

pull/4233/head
Igor Kulikov 6 years ago
parent
commit
10ddb5f8f3
  1. 2
      ui-ngx/src/app/core/api/data-aggregator.ts
  2. 14
      ui-ngx/src/app/core/http/widget.service.ts
  3. 20
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  4. 22
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  5. 38
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html
  6. 7
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss
  7. 78
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts
  8. 6
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
ui-ngx/src/app/core/api/data-aggregator.ts

@ -155,6 +155,7 @@ export class DataAggregator {
}
public onData(data: SubscriptionDataHolder, update: boolean, history: boolean, detectChanges: boolean) {
this.updatedData = true;
if (!this.dataReceived || this.resetPending) {
let updateIntervalScheduledTime = true;
if (!this.dataReceived) {
@ -183,7 +184,6 @@ export class DataAggregator {
this.onInterval(history, detectChanges);
}
}
this.updatedData = true;
}
private onInterval(history?: boolean, detectChanges?: boolean) {

14
ui-ngx/src/app/core/http/widget.service.ts

@ -55,6 +55,8 @@ export class WidgetService {
private systemWidgetsBundles: Array<WidgetsBundle>;
private tenantWidgetsBundles: Array<WidgetsBundle>;
private widgetTypeInfosCache = new Map<string, Array<WidgetTypeInfo>>();
private loadWidgetsBundleCacheSubject: ReplaySubject<any>;
constructor(
@ -137,8 +139,15 @@ export class WidgetService {
public getBundleWidgetTypeInfos(bundleAlias: string, isSystem: boolean,
config?: RequestConfig): Observable<Array<WidgetTypeInfo>> {
return this.http.get<Array<WidgetTypeInfo>>(`/api/widgetTypesInfos?isSystem=${isSystem}&bundleAlias=${bundleAlias}`,
defaultHttpOptionsFromConfig(config));
const key = bundleAlias + (isSystem ? '_sys' : '');
if (this.widgetTypeInfosCache.has(key)) {
return of(this.widgetTypeInfosCache.get(key));
} else {
return this.http.get<Array<WidgetTypeInfo>>(`/api/widgetTypesInfos?isSystem=${isSystem}&bundleAlias=${bundleAlias}`,
defaultHttpOptionsFromConfig(config)).pipe(
tap((res) => this.widgetTypeInfosCache.set(key, res) )
);
}
}
public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean,
@ -305,6 +314,7 @@ export class WidgetService {
this.systemWidgetsBundles = undefined;
this.tenantWidgetsBundles = undefined;
this.loadWidgetsBundleCacheSubject = undefined;
this.widgetTypeInfosCache.clear();
}
}

20
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -242,8 +242,9 @@
</tb-edit-widget>
</tb-details-panel>
<tb-details-panel *ngIf="!isAddingWidgetClosed && !widgetEditMode" fxFlex
headerTitle="{{
(!widgetsBundle?.title ? 'widget.select-widgets-bundle' : 'dashboard.select-widget-value') | translate: widgetsBundle
headerTitle="{{ isAddingWidget ?
((!dashboardWidgetSelectComponent?.widgetsBundle ?
'widget.select-widgets-bundle' : 'dashboard.select-widget-value') | translate: dashboardWidgetSelectComponent?.widgetsBundle) : ''
}}"
headerHeightPx="64"
[isShowSearch]="true"
@ -252,34 +253,33 @@
backgroundColor="#cfd8dc"
(closeDetails)="onAddWidgetClosed()"
(closeSearch)="onCloseSearchBundle()">
<div class="prefix-title-buttons" [fxHide]="!widgetsBundle?.title" style="height: 28px; margin-right: 12px">
<button class="tb-mat-28" mat-icon-button type="button" (click)="widgetBundleSelected(null)">
<div class="prefix-title-buttons" [fxShow]="(isAddingWidget && dashboardWidgetSelectComponent?.widgetsBundle) ? true : false" style="height: 28px; margin-right: 12px">
<button class="tb-mat-28" mat-icon-button type="button" (click)="clearSelectedWidgetBundle()">
<mat-icon>arrow_back</mat-icon>
</button>
</div>
<div class="search-pane" *ngIf="isAddingWidget" fxLayout="row">
<tb-widgets-bundle-search fxFlex
[(ngModel)]="searchBundle"
placeholder="{{ (!widgetsBundle?.title ? 'widgets-bundle.search' : 'widget.search') | translate }}"
placeholder="{{ (!dashboardWidgetSelectComponent?.widgetsBundle ? 'widgets-bundle.search' : 'widget.search') | translate }}"
(ngModelChange)="searchBundle = $event">
</tb-widgets-bundle-search>
</div>
<div class="details-buttons" *ngIf="isAddingWidget">
<button mat-button mat-icon-button type="button"
*ngIf="widgetTypes.length > 1"
*ngIf="dashboardWidgetSelectComponent?.widgetTypes.size > 1"
(click)="editWidgetsTypesToDisplay($event)"
matTooltip="{{ 'widget.filter' | translate }}"
matTooltipPosition="above">
<mat-icon>filter_list</mat-icon>
</button>
</div>
<tb-dashboard-widget-select *ngIf="isAddingWidget"
<tb-dashboard-widget-select #dashboardWidgetSelect
*ngIf="isAddingWidget"
[aliasController]="dashboardCtx.aliasController"
[widgetsBundle]="widgetsBundle"
[searchBundle]="searchBundle"
[filterWidgetTypes]="filterWidgetTypes"
(widgetsTypes)="updateWidgetsTypes($event)"
(widgetsBundleSelected)="widgetBundleSelected($event)"
(widgetsBundleSelected)="widgetBundleSelected()"
(widgetSelected)="addWidgetFromType($event)">
</tb-dashboard-widget-select>
</tb-details-panel>

22
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -121,6 +121,7 @@ import {
DisplayWidgetTypesPanelData,
WidgetTypes
} from '@home/components/dashboard-page/widget-types-panel.component';
import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/dashboard-widget-select.component';
// @dynamic
@Component({
@ -167,9 +168,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
forceDashboardMobileMode = false;
isAddingWidget = false;
isAddingWidgetClosed = true;
widgetsBundle: WidgetsBundle = null;
searchBundle = '';
widgetTypes: WidgetTypes[] = [];
filterWidgetTypes: widgetType[] = null;
isToolbarOpened = false;
@ -267,6 +266,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
@ViewChild('tbEditWidget') editWidgetComponent: EditWidgetComponent;
@ViewChild('dashboardWidgetSelect') dashboardWidgetSelectComponent: DashboardWidgetSelectComponent;
constructor(protected store: Store<AppState>,
@Inject(WINDOW) private window: Window,
private breakpointObserver: BreakpointObserver,
@ -367,7 +368,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.forceDashboardMobileMode = false;
this.isAddingWidget = false;
this.isAddingWidgetClosed = true;
this.widgetsBundle = null;
this.isToolbarOpened = false;
this.isToolbarOpenedAnimate = false;
@ -885,7 +885,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
addWidgetFromType(widget: WidgetInfo) {
this.onAddWidgetClosed();
this.widgetTypes = [];
this.searchBundle = '';
this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe(
(widgetTypeInfo) => {
@ -1153,16 +1152,13 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
return widgetContextActions;
}
widgetBundleSelected(bundle: WidgetsBundle){
this.widgetsBundle = bundle;
this.widgetTypes = [];
widgetBundleSelected(){
this.searchBundle = '';
}
updateWidgetsTypes(types: Set<widgetType>) {
this.widgetTypes = Array.from(types.values()).map(type => {
return {type, display: true};
});
clearSelectedWidgetBundle() {
this.searchBundle = '';
this.dashboardWidgetSelectComponent.widgetsBundle = null;
}
editWidgetsTypesToDisplay($event: Event) {
@ -1191,7 +1187,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
{
provide: DISPLAY_WIDGET_TYPES_PANEL_DATA,
useValue: {
types: this.widgetTypes,
types: Array.from(this.dashboardWidgetSelectComponent.widgetTypes.values()).map(type => {
return {type, display: true};
}),
typesUpdated: (newTypes) => {
this.filterWidgetTypes = newTypes.filter(type => type.display).map(type => type.type);
}

38
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html

@ -17,7 +17,7 @@
-->
<div class="widget-select">
<div *ngIf="widgetsBundle; else bundles">
<div *ngIf="(widgets$ | async)?.length; else emptyBundle" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngIf="(widgets$ | async)?.length; else loadingWidgets" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngFor="let widget of widgets$ | async" class="mat-card-container">
<mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="onWidgetClicked($event, widget)">
<div class="preview-container" fxFlex="45">
@ -33,15 +33,24 @@
</mat-card>
</div>
</div>
<ng-template #emptyBundle>
<span translate
style="display: flex;"
fxLayoutAlign="center center"
class="mat-headline tb-absolute-fill">widgets-bundle.empty</span>
<ng-template #loadingWidgets>
<div *ngIf="loadingWidgets$ | async; else emptyBundle" fxLayout="column"
fxLayoutAlign="center center" class="tb-absolute-fill">
<span class="mat-headline" style="padding-bottom: 20px;">
{{ 'widget.loading-widgets' | translate }}
</span>
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
</div>
<ng-template #emptyBundle>
<span translate
style="display: flex;"
fxLayoutAlign="center center"
class="mat-headline tb-absolute-fill">widgets-bundle.empty</span>
</ng-template>
</ng-template>
</div>
<ng-template #bundles>
<div fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngIf="(widgetsBundles$ | async)?.length; else loadingWidgetBundles" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
<div *ngFor="let widgetsBundle of widgetsBundles$ | async" class="mat-card-container">
<mat-card fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="selectBundle($event, widgetsBundle)">
<div class="preview-container" fxFlex="45">
@ -57,5 +66,20 @@
</mat-card>
</div>
</div>
<ng-template #loadingWidgetBundles>
<div *ngIf="loadingWidgetBundles$ | async; else noWidgetBundles" fxLayout="column"
fxLayoutAlign="center center" class="tb-absolute-fill">
<span class="mat-headline" style="padding-bottom: 20px;">
{{ 'widgets-bundle.loading-widgets-bundles' | translate }}
</span>
<mat-spinner strokeWidth="5"></mat-spinner>
</div>
<ng-template #noWidgetBundles>
<span translate
style="display: flex;"
fxLayoutAlign="center center"
class="mat-headline tb-absolute-fill">widgets-bundle.no-widgets-bundles-text</span>
</ng-template>
</ng-template>
</ng-template>
</div>

7
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss

@ -29,8 +29,15 @@
flex: 0 0 100%;
max-width: 100%;
&:hover {
.mat-card {
box-shadow: 0 2px 6px 6px rgb(0 0 0 / 20%), 0 1px 4px 2px rgb(0 0 0 / 14%), 0 1px 6px 0 rgb(0 0 0 / 12%)
}
}
.mat-card {
cursor: pointer;
transition: box-shadow 0.2s;
.preview-container {
text-align: center;

78
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts

@ -20,8 +20,7 @@ import { IAliasController } from '@core/api/widget-api.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetService } from '@core/http/widget.service';
import { WidgetInfo, widgetType } from '@shared/models/widget.models';
import { toWidgetInfo } from '@home/models/widget-component.models';
import { distinctUntilChanged, map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators';
import { distinctUntilChanged, map, publishReplay, refCount, share, switchMap, tap } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { isDefinedAndNotNull } from '@core/utils';
@ -37,18 +36,28 @@ export class DashboardWidgetSelectComponent implements OnInit {
private filterWidgetTypes$ = new BehaviorSubject<Array<widgetType>>(null);
private widgetsInfo: Observable<Array<WidgetInfo>>;
private widgetsBundleValue: WidgetsBundle;
private widgetsType = new Set<widgetType>();
widgetTypes = new Set<widgetType>();
widgets$: Observable<Array<WidgetInfo>>;
loadingWidgetsSubject: BehaviorSubject<boolean> = new BehaviorSubject(false);
loadingWidgets$ = this.loadingWidgetsSubject.pipe(
share()
);
widgetsBundles$: Observable<Array<WidgetsBundle>>;
loadingWidgetBundlesSubject: BehaviorSubject<boolean> = new BehaviorSubject(true);
loadingWidgetBundles$ = this.loadingWidgetBundlesSubject.pipe(
share()
);
@Input()
set widgetsBundle(widgetBundle: WidgetsBundle) {
this.widgetsInfo = null;
this.widgetsType.clear();
this.widgetsTypes.emit(this.widgetsType);
this.filterWidgetTypes$.next(null);
this.widgetsBundleValue = widgetBundle;
if (this.widgetsBundleValue !== widgetBundle) {
this.widgetsBundleValue = widgetBundle;
if (widgetBundle === null) {
this.widgetTypes.clear();
}
this.filterWidgetTypes$.next(null);
this.widgetsInfo = null;
}
}
get widgetsBundle(): WidgetsBundle {
@ -74,14 +83,8 @@ export class DashboardWidgetSelectComponent implements OnInit {
@Output()
widgetsBundleSelected: EventEmitter<WidgetsBundle> = new EventEmitter<WidgetsBundle>();
@Output()
widgetsTypes: EventEmitter<Set<widgetType>> = new EventEmitter<Set<widgetType>>();
constructor(private widgetsService: WidgetService,
private sanitizer: DomSanitizer) {
}
ngOnInit(): void {
this.widgetsBundles$ = this.search$.asObservable().pipe(
distinctUntilChanged(),
switchMap(search => this.fetchWidgetBundle(search))
@ -92,27 +95,41 @@ export class DashboardWidgetSelectComponent implements OnInit {
);
}
ngOnInit(): void {
}
private getWidgets(): Observable<Array<WidgetInfo>> {
if (!this.widgetsInfo) {
if (this.widgetsBundle !== null) {
const bundleAlias = this.widgetsBundle.alias;
const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID;
this.loadingWidgetsSubject.next(true);
this.widgetsInfo = this.widgetsService.getBundleWidgetTypeInfos(bundleAlias, isSystem).pipe(
map(widgets => widgets.sort((a, b) => b.createdTime - a.createdTime)),
map(widgets => widgets.map((widgetTypeInfo) => {
this.widgetsType.add(widgetTypeInfo.widgetType);
const widget: WidgetInfo = {
isSystemType: isSystem,
bundleAlias,
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.widgetType,
title: widgetTypeInfo.name,
image: widgetTypeInfo.image,
description: widgetTypeInfo.description
};
return widget;
})),
tap(() => this.widgetsTypes.emit(this.widgetsType)),
map(widgets => {
widgets = widgets.sort((a, b) => b.createdTime - a.createdTime);
const widgetTypes = new Set<widgetType>();
const widgetInfos = widgets.map((widgetTypeInfo) => {
widgetTypes.add(widgetTypeInfo.widgetType);
const widget: WidgetInfo = {
isSystemType: isSystem,
bundleAlias,
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.widgetType,
title: widgetTypeInfo.name,
image: widgetTypeInfo.image,
description: widgetTypeInfo.description
};
return widget;
}
);
setTimeout(() => {
this.widgetTypes = widgetTypes;
});
return widgetInfos;
}),
tap(() => {
this.loadingWidgetsSubject.next(false);
}),
publishReplay(1),
refCount()
);
@ -147,6 +164,7 @@ export class DashboardWidgetSelectComponent implements OnInit {
private getWidgetsBundle(): Observable<Array<WidgetsBundle>> {
return this.widgetsService.getAllWidgetsBundles().pipe(
tap(() => this.loadingWidgetBundlesSubject.next(false)),
publishReplay(1),
refCount()
);

6
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2266,7 +2266,8 @@
"data-overflow": "Widget displays {{count}} out of {{total}} entities",
"alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities",
"search": "Search widget",
"filter": "Widget filter type"
"filter": "Widget filter type",
"loading-widgets": "Loading widgets..."
},
"widget-action": {
"header-button": "Widget header button",
@ -2318,7 +2319,8 @@
"invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure.",
"search": "Search widget bundles",
"selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } selected",
"open-widgets-bundle": "Open widgets bundle"
"open-widgets-bundle": "Open widgets bundle",
"loading-widgets-bundles": "Loading widgets bundles..."
},
"widget-config": {
"data": "Data",

Loading…
Cancel
Save