Commit d520415d5bb4e34e879226f36bc05d73d41bebbf

Authored by Vladyslav_Prykhodko
1 parent eaa2c578

UI: Improvement load and update time into time series table

... ... @@ -39,73 +39,75 @@
39 39 </mat-toolbar>
40 40 <mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex
41 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 74 (click)="onActionButtonClick($event, row, actionDescriptor)">
87 75 <mat-icon>{{actionDescriptor.icon}}</mat-icon>
88   - <span>{{ actionDescriptor.displayName }}</span>
89 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 111 </mat-tab>
110 112 </mat-tab-group>
111 113 </div>
... ...
... ... @@ -40,12 +40,12 @@ import {
40 40 } from '@shared/models/widget.models';
41 41 import { UtilsService } from '@core/services/utils.service';
42 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 44 import cssjs from '@core/css/css';
45 45 import { PageLink } from '@shared/models/page/page-link';
46 46 import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
47 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 49 import { emptyPageData, PageData } from '@shared/models/page/page-data';
50 50 import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
51 51 import { MatPaginator } from '@angular/material/paginator';
... ... @@ -129,6 +129,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
129 129 public showTimestamp = true;
130 130 private dateFormatFilter: string;
131 131
  132 + private subscriptions: Subscription[] = [];
  133 +
132 134 private searchAction: WidgetAction = {
133 135 name: 'action.search',
134 136 show: true,
... ... @@ -166,40 +168,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
166 168 debounceTime(150),
167 169 distinctUntilChanged(),
168 170 tap(() => {
169   - if (this.displayPagination) {
170   - this.paginators.forEach((paginator) => {
171   - paginator.pageIndex = 0;
172   - });
173   - }
174 171 this.sources.forEach((source) => {
175 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 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 190 public onDataUpdated() {
200   - this.sources.forEach((source) => {
201   - source.timeseriesDatasource.dataUpdated(this.data);
202   - });
  191 + this.updateCurrentSourceData();
203 192 }
204 193
205 194 private initialize() {
... ... @@ -305,7 +294,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
305 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 316 onSourceIndexChanged() {
  317 + this.updateCurrentSourceData();
309 318 this.updateActiveEntityInfo();
310 319 }
311 320
... ... @@ -326,30 +335,19 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
326 335 exitFilterMode() {
327 336 this.textSearchMode = false;
328 337 this.textSearch = null;
329   - this.sources.forEach((source, index) => {
  338 + this.sources.forEach((source) => {
330 339 source.pageLink.textSearch = this.textSearch;
331   - const sort = this.sorts.toArray()[index];
332   - let paginator = null;
333 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 345 this.ctx.hideTitlePanel = false;
340 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 351 if (this.displayPagination) {
354 352 source.pageLink.page = paginator.pageIndex;
355 353 source.pageLink.pageSize = paginator.pageSize;
... ... @@ -418,7 +416,6 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
418 416
419 417 if (!isDefined(content)) {
420 418 return '';
421   -
422 419 } else {
423 420 switch (typeof content) {
424 421 case 'string':
... ... @@ -462,6 +459,18 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
462 459 }
463 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 476 class TimeseriesDatasource implements DataSource<TimeseriesRow> {
... ... @@ -482,6 +491,10 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
482 491 }
483 492
484 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 498 return this.rowsSubject.asObservable();
486 499 }
487 500
... ... @@ -565,7 +578,8 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
565 578
566 579 private fetchRows(pageLink: PageLink): Observable<PageData<TimeseriesRow>> {
567 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 }
... ...