Browse Source

Merge pull request #4096 from vvlladd28/improvement/timeseries-table/load

UI: Improvement load and update time into time series table
pull/4190/head
Igor Kulikov 6 years ago
committed by GitHub
parent
commit
961455b4af
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 10
      ui-ngx/src/app/core/api/data-aggregator.ts
  3. 4
      ui-ngx/src/app/core/api/entity-data-subscription.ts
  4. 16
      ui-ngx/src/app/core/api/entity-data.service.ts
  5. 1
      ui-ngx/src/app/core/api/widget-api.models.ts
  6. 7
      ui-ngx/src/app/core/api/widget-subscription.ts
  7. 130
      ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html
  8. 97
      ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts
  9. 3
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  10. 1
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  11. 1
      ui-ngx/src/app/shared/models/widget.models.ts

4
application/src/main/data/json/system/widget_bundles/cards.json

@ -47,7 +47,7 @@
"resources": [],
"templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}",
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}"
@ -134,4 +134,4 @@
}
}
]
}
}

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

@ -71,6 +71,7 @@ export class DataAggregator {
private dataReceived = false;
private resetPending = false;
private updatedData = false;
private noAggregation = this.aggregationType === AggregationType.NONE;
private aggregationTimeout = Math.max(this.interval, 1000);
@ -90,7 +91,8 @@ export class DataAggregator {
private timeWindow: number,
private interval: number,
private stateData: boolean,
private utils: UtilsService) {
private utils: UtilsService,
private ignoreDataUpdateOnIntervalTick: boolean) {
this.tsKeyNames.forEach((key) => {
this.dataBuffer[key] = [];
});
@ -140,6 +142,7 @@ export class DataAggregator {
this.elapsed = 0;
this.aggregationTimeout = Math.max(this.interval, 1000);
this.resetPending = true;
this.updatedData = false;
this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout);
}
@ -180,6 +183,7 @@ export class DataAggregator {
this.onInterval(history, detectChanges);
}
}
this.updatedData = true;
}
private onInterval(history?: boolean, detectChanges?: boolean) {
@ -201,8 +205,9 @@ export class DataAggregator {
} else {
this.data = this.updateData();
}
if (this.onDataCb) {
if (this.onDataCb && (!this.ignoreDataUpdateOnIntervalTick || this.updatedData)) {
this.onDataCb(this.data, detectChanges);
this.updatedData = false;
}
if (!history) {
this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout);
@ -223,6 +228,7 @@ export class DataAggregator {
this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue];
}
aggKeyData.delete(aggTimestamp);
this.updatedData = true;
} else if (aggTimestamp <= this.endTs) {
const kvPair: [number, any] = [aggTimestamp, aggData.aggValue];
keyData.push(kvPair);

4
ui-ngx/src/app/core/api/entity-data-subscription.ts

@ -69,6 +69,7 @@ export interface EntityDataSubscriptionOptions {
type: widgetType;
entityFilter?: EntityFilter;
isPaginatedDataSubscription?: boolean;
ignoreDataUpdateOnIntervalTick?: boolean;
pageLink?: EntityDataPageLink;
keyFilters?: Array<KeyFilter>;
additionalKeyFilters?: Array<KeyFilter>;
@ -750,7 +751,8 @@ export class EntityDataSubscription {
subsTw.aggregation.timeWindow,
subsTw.aggregation.interval,
subsTw.aggregation.stateData,
this.utils
this.utils,
this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick
);
}

16
ui-ngx/src/app/core/api/entity-data.service.ts

@ -60,7 +60,8 @@ export class EntityDataService {
constructor(private telemetryService: TelemetryWebsocketService,
private utils: UtilsService) {}
public prepareSubscription(listener: EntityDataListener): Observable<EntityDataLoadResult> {
public prepareSubscription(listener: EntityDataListener,
ignoreDataUpdateOnIntervalTick = false): Observable<EntityDataLoadResult> {
const datasource = listener.configDatasource;
listener.subscriptionOptions = this.createSubscriptionOptions(
datasource,
@ -68,7 +69,8 @@ export class EntityDataService {
datasource.pageLink,
datasource.keyFilters,
null,
false);
false,
ignoreDataUpdateOnIntervalTick);
if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !datasource.pageLink)) {
return of(null);
}
@ -87,7 +89,8 @@ export class EntityDataService {
public subscribeForPaginatedData(listener: EntityDataListener,
pageLink: EntityDataPageLink,
keyFilters: KeyFilter[]): Observable<EntityDataLoadResult> {
keyFilters: KeyFilter[],
ignoreDataUpdateOnIntervalTick = false): Observable<EntityDataLoadResult> {
const datasource = listener.configDatasource;
listener.subscriptionOptions = this.createSubscriptionOptions(
datasource,
@ -95,7 +98,8 @@ export class EntityDataService {
pageLink,
datasource.keyFilters,
keyFilters,
true);
true,
ignoreDataUpdateOnIntervalTick);
if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) {
listener.dataLoaded(emptyPageData<EntityData>(), [],
listener.configDatasourceIndex, listener.subscriptionOptions.pageLink);
@ -119,7 +123,8 @@ export class EntityDataService {
pageLink: EntityDataPageLink,
keyFilters: KeyFilter[],
additionalKeyFilters: KeyFilter[],
isPaginatedDataSubscription: boolean): EntityDataSubscriptionOptions {
isPaginatedDataSubscription: boolean,
ignoreDataUpdateOnIntervalTick: boolean): EntityDataSubscriptionOptions {
const subscriptionDataKeys: Array<SubscriptionDataKey> = [];
datasource.dataKeys.forEach((dataKey) => {
const subscriptionDataKey: SubscriptionDataKey = {
@ -145,6 +150,7 @@ export class EntityDataService {
}
}
entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription;
entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick = ignoreDataUpdateOnIntervalTick;
return entityDataSubscriptionOptions;
}
}

1
ui-ngx/src/app/core/api/widget-api.models.ts

@ -226,6 +226,7 @@ export interface WidgetSubscriptionOptions {
hasDataPageLink?: boolean;
singleEntity?: boolean;
warnOnPageDataOverflow?: boolean;
ignoreDataUpdateOnIntervalTick?: boolean;
targetDeviceAliasIds?: Array<string>;
targetDeviceIds?: Array<string>;
useDashboardTimewindow?: boolean;

7
ui-ngx/src/app/core/api/widget-subscription.ts

@ -83,6 +83,7 @@ export class WidgetSubscription implements IWidgetSubscription {
hasDataPageLink: boolean;
singleEntity: boolean;
warnOnPageDataOverflow: boolean;
ignoreDataUpdateOnIntervalTick: boolean;
datasourcePages: PageData<Datasource>[];
dataPages: PageData<Array<DatasourceData>>[];
@ -200,6 +201,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.hasDataPageLink = options.hasDataPageLink;
this.singleEntity = options.singleEntity;
this.warnOnPageDataOverflow = options.warnOnPageDataOverflow;
this.ignoreDataUpdateOnIntervalTick = options.ignoreDataUpdateOnIntervalTick;
this.datasourcePages = [];
this.datasources = [];
this.dataPages = [];
@ -423,7 +425,7 @@ export class WidgetSubscription implements IWidgetSubscription {
}
};
this.entityDataListeners.push(listener);
return this.ctx.entityDataService.prepareSubscription(listener);
return this.ctx.entityDataService.prepareSubscription(listener, this.ignoreDataUpdateOnIntervalTick);
});
return forkJoin(resolveResultObservables).pipe(
map((resolveResults) => {
@ -815,7 +817,8 @@ export class WidgetSubscription implements IWidgetSubscription {
}
};
this.entityDataListeners[datasourceIndex] = entityDataListener;
return this.ctx.entityDataService.subscribeForPaginatedData(entityDataListener, pageLink, keyFilters);
return this.ctx.entityDataService.subscribeForPaginatedData(entityDataListener, pageLink, keyFilters,
this.ignoreDataUpdateOnIntervalTick);
} else {
return of(null);
}

130
ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html

@ -39,73 +39,75 @@
</mat-toolbar>
<mat-tab-group [ngClass]="{'tb-headless': sources.length === 1}" fxFlex
[(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()">
<mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex" label="{{ source.datasource.name }}">
<div fxFlex class="table-container">
<table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp"
matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear>
<ng-container *ngIf="showTimestamp" [matColumnDef]="'0'">
<mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell>
<mat-cell *matCellDef="let row;"
[innerHTML]="cellContent(source, 0, row, row[0])"
[ngStyle]="cellStyle(source, 0, row[0])">
</mat-cell>
</ng-container>
<ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;">
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell>
<mat-cell *matCellDef="let row;"
[innerHTML]="cellContent(source, h.index, row, row[h.index])"
[ngStyle]="cellStyle(source, h.index, row[h.index])">
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',
maxWidth: (actionCellDescriptors.length * 40) + 'px',
width: (actionCellDescriptors.length * 40) + 'px' }">
</mat-header-cell>
<mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',
maxWidth: (actionCellDescriptors.length * 40) + 'px',
width: (actionCellDescriptors.length * 40) + 'px' }">
<div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
*ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"
matTooltip="{{ actionDescriptor.displayName }}"
matTooltipPosition="above"
(click)="onActionButtonClick($event, row, actionDescriptor)">
<mat-icon>{{actionDescriptor.icon}}</mat-icon>
</button>
</div>
<div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length">
<button mat-button mat-icon-button
(click)="$event.stopPropagation(); ctx.detectChanges();"
[matMenuTriggerFor]="cellActionsMenu">
<mat-icon class="material-icons">more_vert</mat-icon>
</button>
<mat-menu #cellActionsMenu="matMenu" xPosition="before">
<button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"
[disabled]="isLoading$ | async"
<mat-tab *ngFor="let source of sources; trackBy: trackBySourcesIndex; let index = index;" label="{{ source.datasource.name }}">
<ng-template [ngIf]="isActiveTab(index)">
<div fxFlex class="table-container">
<table mat-table [dataSource]="source.timeseriesDatasource" [trackBy]="trackByRowTimestamp"
matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear>
<ng-container *ngIf="showTimestamp" [matColumnDef]="'0'">
<mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</mat-header-cell>
<mat-cell *matCellDef="let row;"
[innerHTML]="cellContent(source, 0, row, row[0])"
[ngStyle]="cellStyle(source, 0, row[0])">
</mat-cell>
</ng-container>
<ng-container [matColumnDef]="h.index + ''" *ngFor="let h of source.header; trackBy: trackByColumnIndex;">
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ h.dataKey.label }} </mat-header-cell>
<mat-cell *matCellDef="let row;"
[innerHTML]="cellContent(source, h.index, row, row[h.index])"
[ngStyle]="cellStyle(source, h.index, row[h.index])">
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',
maxWidth: (actionCellDescriptors.length * 40) + 'px',
width: (actionCellDescriptors.length * 40) + 'px' }">
</mat-header-cell>
<mat-cell *matCellDef="let row" [ngStyle.gt-md]="{ minWidth: (actionCellDescriptors.length * 40) + 'px',
maxWidth: (actionCellDescriptors.length * 40) + 'px',
width: (actionCellDescriptors.length * 40) + 'px' }">
<div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
*ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"
matTooltip="{{ actionDescriptor.displayName }}"
matTooltipPosition="above"
(click)="onActionButtonClick($event, row, actionDescriptor)">
<mat-icon>{{actionDescriptor.icon}}</mat-icon>
<span>{{ actionDescriptor.displayName }}</span>
</button>
</mat-menu>
</div>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row>
<mat-row *matRowDef="let row; columns: source.displayedColumns;"
(click)="onRowClick($event, row)"></mat-row>
</table>
<span [fxShow]="source.timeseriesDatasource.isEmpty() | async"
fxLayoutAlign="center center"
class="no-data-found" translate>widget.no-data-found</span>
</div>
<mat-divider *ngIf="displayPagination"></mat-divider>
<mat-paginator *ngIf="displayPagination"
[length]="source.timeseriesDatasource.total() | async"
[pageIndex]="source.pageLink.page"
[pageSize]="source.pageLink.pageSize"
[pageSizeOptions]="pageSizeOptions"
showFirstLastButtons></mat-paginator>
</div>
<div fxHide fxShow.lt-lg *ngIf="actionCellDescriptors.length">
<button mat-button mat-icon-button
(click)="$event.stopPropagation(); ctx.detectChanges();"
[matMenuTriggerFor]="cellActionsMenu">
<mat-icon class="material-icons">more_vert</mat-icon>
</button>
<mat-menu #cellActionsMenu="matMenu" xPosition="before">
<button mat-menu-item *ngFor="let actionDescriptor of actionCellDescriptors; trackBy: trackByActionCellDescriptionId"
[disabled]="isLoading$ | async"
(click)="onActionButtonClick($event, row, actionDescriptor)">
<mat-icon>{{actionDescriptor.icon}}</mat-icon>
<span>{{ actionDescriptor.displayName }}</span>
</button>
</mat-menu>
</div>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="source.displayedColumns; sticky: true"></mat-header-row>
<mat-row *matRowDef="let row; columns: source.displayedColumns;"
(click)="onRowClick($event, row)"></mat-row>
</table>
<span [fxShow]="source.timeseriesDatasource.isEmpty() | async"
fxLayoutAlign="center center"
class="no-data-found" translate>widget.no-data-found</span>
</div>
<mat-divider *ngIf="displayPagination"></mat-divider>
<mat-paginator *ngIf="displayPagination"
[length]="source.timeseriesDatasource.total() | async"
[pageIndex]="source.pageLink.page"
[pageSize]="source.pageLink.pageSize"
[pageSizeOptions]="pageSizeOptions"
showFirstLastButtons></mat-paginator>
</ng-template>
</mat-tab>
</mat-tab-group>
</div>

97
ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts

@ -40,12 +40,12 @@ import {
} from '@shared/models/widget.models';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import {hashCode, isDefined, isDefinedAndNotNull, isNumber} from '@core/utils';
import { hashCode, isDefined, isNumber } from '@core/utils';
import cssjs from '@core/css/css';
import { PageLink } from '@shared/models/page/page-link';
import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs';
import { BehaviorSubject, fromEvent, merge, Observable, of, Subscription } from 'rxjs';
import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
import { MatPaginator } from '@angular/material/paginator';
@ -129,6 +129,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
public showTimestamp = true;
private dateFormatFilter: string;
private subscriptions: Subscription[] = [];
private searchAction: WidgetAction = {
name: 'action.search',
show: true,
@ -166,40 +168,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
debounceTime(150),
distinctUntilChanged(),
tap(() => {
if (this.displayPagination) {
this.paginators.forEach((paginator) => {
paginator.pageIndex = 0;
});
}
this.sources.forEach((source) => {
source.pageLink.textSearch = this.textSearch;
if (this.displayPagination) {
source.pageLink.page = 0;
}
});
this.updateAllData();
this.loadCurrentSourceRow();
this.ctx.detectChanges();
})
)
.subscribe();
if (this.displayPagination) {
this.sorts.forEach((sort, index) => {
sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0);
});
}
this.sorts.forEach((sort, index) => {
const paginator = this.displayPagination ? this.paginators.toArray()[index] : null;
sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0);
((this.displayPagination ? merge(sort.sortChange, paginator.page) : sort.sortChange) as Observable<any>)
.pipe(
tap(() => this.updateData(sort, paginator, index))
)
.subscribe();
this.sorts.changes.subscribe(() => {
this.initSubscriptionsToSortAndPaginator();
});
this.updateAllData();
this.initSubscriptionsToSortAndPaginator();
}
public onDataUpdated() {
this.sources.forEach((source) => {
source.timeseriesDatasource.dataUpdated(this.data);
});
this.updateCurrentSourceData();
}
private initialize() {
@ -305,7 +294,27 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
this.ctx.activeEntityInfo = activeEntityInfo;
}
private initSubscriptionsToSortAndPaginator() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
this.sorts.forEach((sort, index) => {
let paginator = null;
const observables = [sort.sortChange];
if (this.displayPagination) {
paginator = this.paginators.toArray()[index];
this.subscriptions.push(
sort.sortChange.subscribe(() => paginator.pageIndex = 0)
);
observables.push(paginator.page);
}
this.updateData(sort, paginator);
this.subscriptions.push(merge(...observables).pipe(
tap(() => this.updateData(sort, paginator))
).subscribe());
});
}
onSourceIndexChanged() {
this.updateCurrentSourceData();
this.updateActiveEntityInfo();
}
@ -326,30 +335,19 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
exitFilterMode() {
this.textSearchMode = false;
this.textSearch = null;
this.sources.forEach((source, index) => {
this.sources.forEach((source) => {
source.pageLink.textSearch = this.textSearch;
const sort = this.sorts.toArray()[index];
let paginator = null;
if (this.displayPagination) {
paginator = this.paginators.toArray()[index];
paginator.pageIndex = 0;
source.pageLink.page = 0;
}
this.updateData(sort, paginator, index);
});
this.loadCurrentSourceRow();
this.ctx.hideTitlePanel = false;
this.ctx.detectChanges(true);
}
private updateAllData() {
this.sources.forEach((source, index) => {
const sort = this.sorts.toArray()[index];
const paginator = this.displayPagination ? this.paginators.toArray()[index] : null;
this.updateData(sort, paginator, index);
});
}
private updateData(sort: MatSort, paginator: MatPaginator, index: number) {
const source = this.sources[index];
private updateData(sort: MatSort, paginator: MatPaginator) {
const source = this.sources[this.sourceIndex];
if (this.displayPagination) {
source.pageLink.page = paginator.pageIndex;
source.pageLink.pageSize = paginator.pageSize;
@ -418,7 +416,6 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
if (!isDefined(content)) {
return '';
} else {
switch (typeof content) {
case 'string':
@ -462,6 +459,18 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
}
this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row, entityLabel);
}
public isActiveTab(index: number): boolean {
return index === this.sourceIndex;
}
private updateCurrentSourceData() {
this.sources[this.sourceIndex].timeseriesDatasource.dataUpdated(this.data);
}
private loadCurrentSourceRow() {
this.sources[this.sourceIndex].timeseriesDatasource.loadRows();
}
}
class TimeseriesDatasource implements DataSource<TimeseriesRow> {
@ -482,6 +491,10 @@ class TimeseriesDatasource implements DataSource<TimeseriesRow> {
}
connect(collectionViewer: CollectionViewer): Observable<TimeseriesRow[] | ReadonlyArray<TimeseriesRow>> {
if (this.rowsSubject.isStopped) {
this.rowsSubject.isStopped = false;
this.pageDataSubject.isStopped = false;
}
return this.rowsSubject.asObservable();
}

3
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -485,6 +485,9 @@ export class WidgetComponentService {
if (isUndefined(result.typeParameters.warnOnPageDataOverflow)) {
result.typeParameters.warnOnPageDataOverflow = true;
}
if (isUndefined(result.typeParameters.ignoreDataUpdateOnIntervalTick)) {
result.typeParameters.ignoreDataUpdateOnIntervalTick = false;
}
if (isUndefined(result.typeParameters.dataKeysOptional)) {
result.typeParameters.dataKeysOptional = false;
}

1
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -895,6 +895,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
hasDataPageLink: this.typeParameters.hasDataPageLink,
singleEntity: this.typeParameters.singleEntity,
warnOnPageDataOverflow: this.typeParameters.warnOnPageDataOverflow,
ignoreDataUpdateOnIntervalTick: this.typeParameters.ignoreDataUpdateOnIntervalTick,
comparisonEnabled: comparisonSettings.comparisonEnabled,
timeForComparison: comparisonSettings.timeForComparison
};

1
ui-ngx/src/app/shared/models/widget.models.ts

@ -154,6 +154,7 @@ export interface WidgetTypeParameters {
hasDataPageLink?: boolean;
singleEntity?: boolean;
warnOnPageDataOverflow?: boolean;
ignoreDataUpdateOnIntervalTick?: boolean;
}
export interface WidgetControllerDescriptor {

Loading…
Cancel
Save