Commit d520415d5bb4e34e879226f36bc05d73d41bebbf

Authored by Vladyslav_Prykhodko
1 parent eaa2c578

UI: Improvement load and update time into time series table

@@ -39,73 +39,75 @@ @@ -39,73 +39,75 @@
39 </mat-toolbar> 39 </mat-toolbar>
40 <mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex 40 <mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex
41 [(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()"> 41 [(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()">
42 - <mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex" label="{{ source.datasource.name }}">  
43 - <div fxFlex class="table-container">  
44 - <table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp"  
45 - matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear>  
46 - <ng-container *ngIf="showTimestamp" [matColumnDef]="'0'">  
47 - <mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell>  
48 - <mat-cell *matCellDef="let row;"  
49 - [innerHTML]="cellContent(source, 0, row, row[0])"  
50 - [ngStyle]="cellStyle(source, 0, row[0])">  
51 - </mat-cell>  
52 - </ng-container>  
53 - <ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;">  
54 - <mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell>  
55 - <mat-cell *matCellDef="let row;"  
56 - [innerHTML]="cellContent(source, h.index, row, row[h.index])"  
57 - [ngStyle]="cellStyle(source, h.index, row[h.index])">  
58 - </mat-cell>  
59 - </ng-container>  
60 - <ng-container matColumnDef="actions" stickyEnd>  
61 - <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',  
62 - maxWidth: (actionCellDescriptors.length * 40) + 'px',  
63 - width: (actionCellDescriptors.length * 40) + 'px' }">  
64 - </mat-header-cell>  
65 - <mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',  
66 - maxWidth: (actionCellDescriptors.length * 40) + 'px',  
67 - width: (actionCellDescriptors.length * 40) + 'px' }">  
68 - <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">  
69 - <button mat-button mat-icon-button [disabled]="isLoading$ | async"  
70 - *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"  
71 - matTooltip="{{ actionDescriptor.displayName }}"  
72 - matTooltipPosition="above"  
73 - (click)="onActionButtonClick($event, row, actionDescriptor)">  
74 - <mat-icon>{{actionDescriptor.icon}}</mat-icon>  
75 - </button>  
76 - </div>  
77 - <div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length">  
78 - <button mat-button mat-icon-button  
79 - (click)="$event.stopPropagation(); ctx.detectChanges();"  
80 - [matMenuTriggerFor]="cellActionsMenu">  
81 - <mat-icon class="material-icons">more_vert</mat-icon>  
82 - </button>  
83 - <mat-menu #cellActionsMenu="matMenu" xPosition="before">  
84 - <button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"  
85 - [disabled]="isLoading$ | async" 42 + <mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex; let index = index;" label="{{ source.datasource.name }}">
  43 + <ng-template [ngIf]="isActiveTab(index)">
  44 + <div fxFlex class="table-container">
  45 + <table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp"
  46 + matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear>
  47 + <ng-container *ngIf="showTimestamp" [matColumnDef]="'0'">
  48 + <mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell>
  49 + <mat-cell *matCellDef="let row;"
  50 + [innerHTML]="cellContent(source, 0, row, row[0])"
  51 + [ngStyle]="cellStyle(source, 0, row[0])">
  52 + </mat-cell>
  53 + </ng-container>
  54 + <ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;">
  55 + <mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell>
  56 + <mat-cell *matCellDef="let row;"
  57 + [innerHTML]="cellContent(source, h.index, row, row[h.index])"
  58 + [ngStyle]="cellStyle(source, h.index, row[h.index])">
  59 + </mat-cell>
  60 + </ng-container>
  61 + <ng-container matColumnDef="actions" stickyEnd>
  62 + <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',
  63 + maxWidth: (actionCellDescriptors.length * 40) + 'px',
  64 + width: (actionCellDescriptors.length * 40) + 'px' }">
  65 + </mat-header-cell>
  66 + <mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',
  67 + maxWidth: (actionCellDescriptors.length * 40) + 'px',
  68 + width: (actionCellDescriptors.length * 40) + 'px' }">
  69 + <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
  70 + <button mat-button mat-icon-button [disabled]="isLoading$ | async"
  71 + *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"
  72 + matTooltip="{{ actionDescriptor.displayName }}"
  73 + matTooltipPosition="above"
86 (click)="onActionButtonClick($event, row, actionDescriptor)"> 74 (click)="onActionButtonClick($event, row, actionDescriptor)">
87 <mat-icon>{{actionDescriptor.icon}}</mat-icon> 75 <mat-icon>{{actionDescriptor.icon}}</mat-icon>
88 - <span>{{ actionDescriptor.displayName }}</span>  
89 </button> 76 </button>
90 - </mat-menu>  
91 - </div>  
92 - </mat-cell>  
93 - </ng-container>  
94 - <mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row>  
95 - <mat-row *matRowDef="let row; columns: source.displayedColumns;"  
96 - (click)="onRowClick($event, row)"></mat-row>  
97 - </table>  
98 - <span [fxShow]="source.timeseriesDatasource.isEmpty() | async"  
99 - fxLayoutAlign="center center"  
100 - class="no-data-found" translate>widget.no-data-found</span>  
101 - </div>  
102 - <mat-divider *ngIf="displayPagination"></mat-divider>  
103 - <mat-paginator *ngIf="displayPagination"  
104 - [length]="source.timeseriesDatasource.total() | async"  
105 - [pageIndex]="source.pageLink.page"  
106 - [pageSize]="source.pageLink.pageSize"  
107 - [pageSizeOptions]="pageSizeOptions"  
108 - showFirstLastButtons></mat-paginator> 77 + </div>
  78 + <div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length">
  79 + <button mat-button mat-icon-button
  80 + (click)="$event.stopPropagation(); ctx.detectChanges();"
  81 + [matMenuTriggerFor]="cellActionsMenu">
  82 + <mat-icon class="material-icons">more_vert</mat-icon>
  83 + </button>
  84 + <mat-menu #cellActionsMenu="matMenu" xPosition="before">
  85 + <button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"
  86 + [disabled]="isLoading$ | async"
  87 + (click)="onActionButtonClick($event, row, actionDescriptor)">
  88 + <mat-icon>{{actionDescriptor.icon}}</mat-icon>
  89 + <span>{{ actionDescriptor.displayName }}</span>
  90 + </button>
  91 + </mat-menu>
  92 + </div>
  93 + </mat-cell>
  94 + </ng-container>
  95 + <mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row>
  96 + <mat-row *matRowDef="let row; columns: source.displayedColumns;"
  97 + (click)="onRowClick($event, row)"></mat-row>
  98 + </table>
  99 + <span [fxShow]="source.timeseriesDatasource.isEmpty() | async"
  100 + fxLayoutAlign="center center"
  101 + class="no-data-found" translate>widget.no-data-found</span>
  102 + </div>
  103 + <mat-divider *ngIf="displayPagination"></mat-divider>
  104 + <mat-paginator *ngIf="displayPagination"
  105 + [length]="source.timeseriesDatasource.total() | async"
  106 + [pageIndex]="source.pageLink.page"
  107 + [pageSize]="source.pageLink.pageSize"
  108 + [pageSizeOptions]="pageSizeOptions"
  109 + showFirstLastButtons></mat-paginator>
  110 + </ng-template>
109 </mat-tab> 111 </mat-tab>
110 </mat-tab-group> 112 </mat-tab-group>
111 </div> 113 </div>
@@ -40,12 +40,12 @@ import { @@ -40,12 +40,12 @@ import {
40 } from '@shared/models/widget.models'; 40 } from '@shared/models/widget.models';
41 import { UtilsService } from '@core/services/utils.service'; 41 import { UtilsService } from '@core/services/utils.service';
42 import { TranslateService } from '@ngx-translate/core'; 42 import { TranslateService } from '@ngx-translate/core';
43 -import {hashCode, isDefined, isDefinedAndNotNull, isNumber} from '@core/utils'; 43 +import { hashCode, isDefined, isEqual, isNumber } from '@core/utils';
44 import cssjs from '@core/css/css'; 44 import cssjs from '@core/css/css';
45 import { PageLink } from '@shared/models/page/page-link'; 45 import { PageLink } from '@shared/models/page/page-link';
46 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; 46 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
47 import { CollectionViewer, DataSource } from '@angular/cdk/collections'; 47 import { CollectionViewer, DataSource } from '@angular/cdk/collections';
48 -import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; 48 +import { BehaviorSubject, fromEvent, merge, Observable, of, Subscription } from 'rxjs';
49 import { emptyPageData, PageData } from '@shared/models/page/page-data'; 49 import { emptyPageData, PageData } from '@shared/models/page/page-data';
50 import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; 50 import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
51 import { MatPaginator } from '@angular/material/paginator'; 51 import { MatPaginator } from '@angular/material/paginator';
@@ -129,6 +129,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI @@ -129,6 +129,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
129 public showTimestamp = true; 129 public showTimestamp = true;
130 private dateFormatFilter: string; 130 private dateFormatFilter: string;
131 131
  132 + private subscriptions: Subscription[] = [];
  133 +
132 private searchAction: WidgetAction = { 134 private searchAction: WidgetAction = {
133 name: 'action.search', 135 name: 'action.search',
134 show: true, 136 show: true,
@@ -166,40 +168,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI @@ -166,40 +168,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
166 debounceTime(150), 168 debounceTime(150),
167 distinctUntilChanged(), 169 distinctUntilChanged(),
168 tap(() => { 170 tap(() => {
169 - if (this.displayPagination) {  
170 - this.paginators.forEach((paginator) => {  
171 - paginator.pageIndex = 0;  
172 - });  
173 - }  
174 this.sources.forEach((source) => { 171 this.sources.forEach((source) => {
175 source.pageLink.textSearch = this.textSearch; 172 source.pageLink.textSearch = this.textSearch;
  173 + if (this.displayPagination) {
  174 + source.pageLink.page = 0;
  175 + }
176 }); 176 });
177 - this.updateAllData(); 177 + this.loadCurrentSourceRow();
  178 + this.ctx.detectChanges();
178 }) 179 })
179 ) 180 )
180 .subscribe(); 181 .subscribe();
181 182
182 - if (this.displayPagination) {  
183 - this.sorts.forEach((sort, index) => {  
184 - sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0);  
185 - });  
186 - }  
187 - this.sorts.forEach((sort, index) => {  
188 - const paginator = this.displayPagination ? this.paginators.toArray()[index] : null;  
189 - sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0);  
190 - ((this.displayPagination ? merge(sort.sortChange, paginator.page) : sort.sortChange) as Observable<any>)  
191 - .pipe(  
192 - tap(() => this.updateData(sort, paginator, index))  
193 - )  
194 - .subscribe(); 183 + this.sorts.changes.subscribe(() => {
  184 + this.initSubscriptionsToSortAndPaginator();
195 }); 185 });
196 - this.updateAllData(); 186 +
  187 + this.initSubscriptionsToSortAndPaginator();
197 } 188 }
198 189
199 public onDataUpdated() { 190 public onDataUpdated() {
200 - this.sources.forEach((source) => {  
201 - source.timeseriesDatasource.dataUpdated(this.data);  
202 - }); 191 + this.updateCurrentSourceData();
203 } 192 }
204 193
205 private initialize() { 194 private initialize() {
@@ -305,7 +294,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI @@ -305,7 +294,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
305 this.ctx.activeEntityInfo = activeEntityInfo; 294 this.ctx.activeEntityInfo = activeEntityInfo;
306 } 295 }
307 296
  297 + private initSubscriptionsToSortAndPaginator() {
  298 + this.subscriptions.forEach(subscription => subscription.unsubscribe());
  299 + this.sorts.forEach((sort, index) => {
  300 + let paginator = null;
  301 + const observables = [sort.sortChange];
  302 + if (this.displayPagination) {
  303 + paginator = this.paginators.toArray()[index];
  304 + this.subscriptions.push(
  305 + sort.sortChange.subscribe(() => paginator.pageIndex = 0)
  306 + );
  307 + observables.push(paginator.page);
  308 + }
  309 + this.updateData(sort, paginator);
  310 + this.subscriptions.push(merge(...observables).pipe(
  311 + tap(() => this.updateData(sort, paginator))
  312 + ).subscribe());
  313 + });
  314 + }
  315 +
308 onSourceIndexChanged() { 316 onSourceIndexChanged() {
  317 + this.updateCurrentSourceData();
309 this.updateActiveEntityInfo(); 318 this.updateActiveEntityInfo();
310 } 319 }
311 320
@@ -326,30 +335,19 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI @@ -326,30 +335,19 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
326 exitFilterMode() { 335 exitFilterMode() {
327 this.textSearchMode = false; 336 this.textSearchMode = false;
328 this.textSearch = null; 337 this.textSearch = null;
329 - this.sources.forEach((source, index) => { 338 + this.sources.forEach((source) => {
330 source.pageLink.textSearch = this.textSearch; 339 source.pageLink.textSearch = this.textSearch;
331 - const sort = this.sorts.toArray()[index];  
332 - let paginator = null;  
333 if (this.displayPagination) { 340 if (this.displayPagination) {
334 - paginator = this.paginators.toArray()[index];  
335 - paginator.pageIndex = 0; 341 + source.pageLink.page = 0;
336 } 342 }
337 - this.updateData(sort, paginator, index);  
338 }); 343 });
  344 + this.loadCurrentSourceRow();
339 this.ctx.hideTitlePanel = false; 345 this.ctx.hideTitlePanel = false;
340 this.ctx.detectChanges(true); 346 this.ctx.detectChanges(true);
341 } 347 }
342 348
343 - private updateAllData() {  
344 - this.sources.forEach((source, index) => {  
345 - const sort = this.sorts.toArray()[index];  
346 - const paginator = this.displayPagination ? this.paginators.toArray()[index] : null;  
347 - this.updateData(sort, paginator, index);  
348 - });  
349 - }  
350 -  
351 - private updateData(sort: MatSort, paginator: MatPaginator, index: number) {  
352 - const source = this.sources[index]; 349 + private updateData(sort: MatSort, paginator: MatPaginator) {
  350 + const source = this.sources[this.sourceIndex];
353 if (this.displayPagination) { 351 if (this.displayPagination) {
354 source.pageLink.page = paginator.pageIndex; 352 source.pageLink.page = paginator.pageIndex;
355 source.pageLink.pageSize = paginator.pageSize; 353 source.pageLink.pageSize = paginator.pageSize;
@@ -418,7 +416,6 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI @@ -418,7 +416,6 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
418 416
419 if (!isDefined(content)) { 417 if (!isDefined(content)) {
420 return ''; 418 return '';
421 -  
422 } else { 419 } else {
423 switch (typeof content) { 420 switch (typeof content) {
424 case 'string': 421 case 'string':
@@ -462,6 +459,18 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI @@ -462,6 +459,18 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
462 } 459 }
463 this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row, entityLabel); 460 this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row, entityLabel);
464 } 461 }
  462 +
  463 + public isActiveTab(index: number): boolean {
  464 + return index === this.sourceIndex;
  465 + }
  466 +
  467 + private updateCurrentSourceData() {
  468 + this.sources[this.sourceIndex].timeseriesDatasource.dataUpdated(this.data);
  469 + }
  470 +
  471 + private loadCurrentSourceRow() {
  472 + this.sources[this.sourceIndex].timeseriesDatasource.loadRows();
  473 + }
465 } 474 }
466 475
467 class TimeseriesDatasource implements DataSource<TimeseriesRow> { 476 class TimeseriesDatasource implements DataSource<TimeseriesRow> {
@@ -482,6 +491,10 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> { @@ -482,6 +491,10 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
482 } 491 }
483 492
484 connect(collectionViewer: CollectionViewer): Observable<TimeseriesRow[] | ReadonlyArray<TimeseriesRow>> { 493 connect(collectionViewer: CollectionViewer): Observable<TimeseriesRow[] | ReadonlyArray<TimeseriesRow>> {
  494 + if (this.rowsSubject.isStopped) {
  495 + this.rowsSubject.isStopped = false;
  496 + this.pageDataSubject.isStopped = false;
  497 + }
485 return this.rowsSubject.asObservable(); 498 return this.rowsSubject.asObservable();
486 } 499 }
487 500
@@ -565,7 +578,8 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> { @@ -565,7 +578,8 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
565 578
566 private fetchRows(pageLink: PageLink): Observable<PageData<TimeseriesRow>> { 579 private fetchRows(pageLink: PageLink): Observable<PageData<TimeseriesRow>> {
567 return this.allRows$.pipe( 580 return this.allRows$.pipe(
568 - map((data) => pageLink.filterData(data)) 581 + map((data) => pageLink.filterData(data)),
  582 + distinctUntilChanged((prev, curr) => isEqual(prev, curr))
569 ); 583 );
570 } 584 }
571 } 585 }