From d520415d5bb4e34e879226f36bc05d73d41bebbf Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 12 Feb 2021 17:08:43 +0200 Subject: [PATCH 1/3] UI: Improvement load and update time into time series table --- .../timeseries-table-widget.component.html | 130 +++++++++--------- .../lib/timeseries-table-widget.component.ts | 100 ++++++++------ 2 files changed, 123 insertions(+), 107 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index 5f8c94727d..0b41d35785 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -39,73 +39,75 @@ - -
- - - Timestamp - - - - - {{ h.dataKey.label }} - - - - - - - -
- -
-
- - -
+ + Timestamp + + + + + {{ h.dataKey.label }} + + + + + + + +
+ - -
-
-
- - -
- widget.no-data-found -
- - + +
+ + + + +
+ + + + + + widget.no-data-found + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index 4ce340d8ea..a1e8c76ef6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/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, isEqual, 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) - .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 { @@ -482,6 +491,10 @@ class TimeseriesDatasource implements DataSource { } connect(collectionViewer: CollectionViewer): Observable> { + if (this.rowsSubject.isStopped) { + this.rowsSubject.isStopped = false; + this.pageDataSubject.isStopped = false; + } return this.rowsSubject.asObservable(); } @@ -565,7 +578,8 @@ class TimeseriesDatasource implements DataSource { private fetchRows(pageLink: PageLink): Observable> { return this.allRows$.pipe( - map((data) => pageLink.filterData(data)) + map((data) => pageLink.filterData(data)), + distinctUntilChanged((prev, curr) => isEqual(prev, curr)) ); } } From 10cea37abe9249459a4e43e6a4e42edcba06f774 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 15 Feb 2021 18:41:44 +0200 Subject: [PATCH 2/3] UI: Add new setting for subscription reloadOnlyOnDataUpdated --- .../data/json/system/widget_bundles/cards.json | 4 ++-- ui-ngx/src/app/core/api/data-aggregator.ts | 10 ++++++++-- .../src/app/core/api/entity-data-subscription.ts | 4 +++- ui-ngx/src/app/core/api/entity-data.service.ts | 16 +++++++++++----- ui-ngx/src/app/core/api/widget-api.models.ts | 1 + ui-ngx/src/app/core/api/widget-subscription.ts | 6 ++++-- .../lib/timeseries-table-widget.component.ts | 5 ++--- .../widget/widget-component.service.ts | 3 +++ .../home/components/widget/widget.component.ts | 1 + ui-ngx/src/app/shared/models/widget.models.ts | 1 + 10 files changed, 36 insertions(+), 15 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 0d97afd5e7..c0f0d329b3 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -47,7 +47,7 @@ "resources": [], "templateHtml": "\n", "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 reloadOnlyOnDataUpdated: 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 @@ } } ] -} \ No newline at end of file +} diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index b78c0ad029..382c97bfcc 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/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 isReloadOnlyOnDataUpdated: 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.isReloadOnlyOnDataUpdated || 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); diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index 6a8e21a389..44bbfd4c08 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -66,6 +66,7 @@ export interface EntityDataSubscriptionOptions { type: widgetType; entityFilter?: EntityFilter; isPaginatedDataSubscription?: boolean; + isReloadOnlyOnDataUpdated?: boolean; pageLink?: EntityDataPageLink; keyFilters?: Array; additionalKeyFilters?: Array; @@ -671,7 +672,8 @@ export class EntityDataSubscription { subsTw.aggregation.timeWindow, subsTw.aggregation.interval, subsTw.aggregation.stateData, - this.utils + this.utils, + this.entityDataSubscriptionOptions.isReloadOnlyOnDataUpdated ); } diff --git a/ui-ngx/src/app/core/api/entity-data.service.ts b/ui-ngx/src/app/core/api/entity-data.service.ts index c02f36a290..bd1a55c880 100644 --- a/ui-ngx/src/app/core/api/entity-data.service.ts +++ b/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 { + public prepareSubscription(listener: EntityDataListener, + isReloadOnlyOnDataUpdated = false): Observable { const datasource = listener.configDatasource; listener.subscriptionOptions = this.createSubscriptionOptions( datasource, @@ -68,7 +69,8 @@ export class EntityDataService { datasource.pageLink, datasource.keyFilters, null, - false); + false, + isReloadOnlyOnDataUpdated); 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 { + keyFilters: KeyFilter[], + isReloadOnlyOnDataUpdated = false): Observable { const datasource = listener.configDatasource; listener.subscriptionOptions = this.createSubscriptionOptions( datasource, @@ -95,7 +98,8 @@ export class EntityDataService { pageLink, datasource.keyFilters, keyFilters, - true); + true, + isReloadOnlyOnDataUpdated); if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) { listener.dataLoaded(emptyPageData(), [], listener.configDatasourceIndex, listener.subscriptionOptions.pageLink); @@ -119,7 +123,8 @@ export class EntityDataService { pageLink: EntityDataPageLink, keyFilters: KeyFilter[], additionalKeyFilters: KeyFilter[], - isPaginatedDataSubscription: boolean): EntityDataSubscriptionOptions { + isPaginatedDataSubscription: boolean, + isReloadOnlyOnDataUpdated: boolean): EntityDataSubscriptionOptions { const subscriptionDataKeys: Array = []; datasource.dataKeys.forEach((dataKey) => { const subscriptionDataKey: SubscriptionDataKey = { @@ -142,6 +147,7 @@ export class EntityDataService { entityDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; } entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription; + entityDataSubscriptionOptions.isReloadOnlyOnDataUpdated = isReloadOnlyOnDataUpdated; return entityDataSubscriptionOptions; } } diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 1579ae8d1e..e6cbb4108e 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -226,6 +226,7 @@ export interface WidgetSubscriptionOptions { hasDataPageLink?: boolean; singleEntity?: boolean; warnOnPageDataOverflow?: boolean; + reloadOnlyOnDataUpdated?: boolean; targetDeviceAliasIds?: Array; targetDeviceIds?: Array; useDashboardTimewindow?: boolean; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 3e3537941b..7bef682d36 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -83,6 +83,7 @@ export class WidgetSubscription implements IWidgetSubscription { hasDataPageLink: boolean; singleEntity: boolean; warnOnPageDataOverflow: boolean; + reloadOnlyOnDataUpdated: boolean; datasourcePages: PageData[]; dataPages: PageData>[]; @@ -200,6 +201,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.hasDataPageLink = options.hasDataPageLink; this.singleEntity = options.singleEntity; this.warnOnPageDataOverflow = options.warnOnPageDataOverflow; + this.reloadOnlyOnDataUpdated = options.reloadOnlyOnDataUpdated; 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.reloadOnlyOnDataUpdated); }); return forkJoin(resolveResultObservables).pipe( map((resolveResults) => { @@ -815,7 +817,7 @@ 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.reloadOnlyOnDataUpdated); } else { return of(null); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index a1e8c76ef6..937185c7f0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -40,7 +40,7 @@ import { } from '@shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; -import { hashCode, isDefined, isEqual, 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'; @@ -578,8 +578,7 @@ class TimeseriesDatasource implements DataSource { private fetchRows(pageLink: PageLink): Observable> { return this.allRows$.pipe( - map((data) => pageLink.filterData(data)), - distinctUntilChanged((prev, curr) => isEqual(prev, curr)) + map((data) => pageLink.filterData(data)) ); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index 2b5c001628..3a9db04714 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/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.reloadOnlyOnDataUpdated)) { + result.typeParameters.reloadOnlyOnDataUpdated = false; + } if (isUndefined(result.typeParameters.dataKeysOptional)) { result.typeParameters.dataKeysOptional = false; } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index 7838f7e7ba..9d2db23d83 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/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, + reloadOnlyOnDataUpdated: this.typeParameters.reloadOnlyOnDataUpdated, comparisonEnabled: comparisonSettings.comparisonEnabled, timeForComparison: comparisonSettings.timeForComparison }; diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 179a6ff47b..12734ce364 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -154,6 +154,7 @@ export interface WidgetTypeParameters { hasDataPageLink?: boolean; singleEntity?: boolean; warnOnPageDataOverflow?: boolean; + reloadOnlyOnDataUpdated?: boolean; } export interface WidgetControllerDescriptor { From cbc8991b05833bef9db458fae46436ede0b101a0 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 2 Mar 2021 13:21:53 +0200 Subject: [PATCH 3/3] UI: Updated value name --- .../main/data/json/system/widget_bundles/cards.json | 2 +- ui-ngx/src/app/core/api/data-aggregator.ts | 4 ++-- ui-ngx/src/app/core/api/entity-data-subscription.ts | 4 ++-- ui-ngx/src/app/core/api/entity-data.service.ts | 12 ++++++------ ui-ngx/src/app/core/api/widget-api.models.ts | 2 +- ui-ngx/src/app/core/api/widget-subscription.ts | 9 +++++---- .../components/widget/widget-component.service.ts | 4 ++-- .../home/components/widget/widget.component.ts | 2 +- ui-ngx/src/app/shared/models/widget.models.ts | 2 +- 9 files changed, 21 insertions(+), 20 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index c0f0d329b3..437d56678e 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -47,7 +47,7 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n reloadOnlyOnDataUpdated: 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}", + "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\"}" diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index 382c97bfcc..4437538719 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -92,7 +92,7 @@ export class DataAggregator { private interval: number, private stateData: boolean, private utils: UtilsService, - private isReloadOnlyOnDataUpdated: boolean) { + private ignoreDataUpdateOnIntervalTick: boolean) { this.tsKeyNames.forEach((key) => { this.dataBuffer[key] = []; }); @@ -205,7 +205,7 @@ export class DataAggregator { } else { this.data = this.updateData(); } - if (this.onDataCb && (!this.isReloadOnlyOnDataUpdated || this.updatedData)) { + if (this.onDataCb && (!this.ignoreDataUpdateOnIntervalTick || this.updatedData)) { this.onDataCb(this.data, detectChanges); this.updatedData = false; } diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index 44bbfd4c08..86fa80b794 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -66,7 +66,7 @@ export interface EntityDataSubscriptionOptions { type: widgetType; entityFilter?: EntityFilter; isPaginatedDataSubscription?: boolean; - isReloadOnlyOnDataUpdated?: boolean; + ignoreDataUpdateOnIntervalTick?: boolean; pageLink?: EntityDataPageLink; keyFilters?: Array; additionalKeyFilters?: Array; @@ -673,7 +673,7 @@ export class EntityDataSubscription { subsTw.aggregation.interval, subsTw.aggregation.stateData, this.utils, - this.entityDataSubscriptionOptions.isReloadOnlyOnDataUpdated + this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick ); } diff --git a/ui-ngx/src/app/core/api/entity-data.service.ts b/ui-ngx/src/app/core/api/entity-data.service.ts index bd1a55c880..198b5c559a 100644 --- a/ui-ngx/src/app/core/api/entity-data.service.ts +++ b/ui-ngx/src/app/core/api/entity-data.service.ts @@ -61,7 +61,7 @@ export class EntityDataService { private utils: UtilsService) {} public prepareSubscription(listener: EntityDataListener, - isReloadOnlyOnDataUpdated = false): Observable { + ignoreDataUpdateOnIntervalTick = false): Observable { const datasource = listener.configDatasource; listener.subscriptionOptions = this.createSubscriptionOptions( datasource, @@ -70,7 +70,7 @@ export class EntityDataService { datasource.keyFilters, null, false, - isReloadOnlyOnDataUpdated); + ignoreDataUpdateOnIntervalTick); if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !datasource.pageLink)) { return of(null); } @@ -90,7 +90,7 @@ export class EntityDataService { public subscribeForPaginatedData(listener: EntityDataListener, pageLink: EntityDataPageLink, keyFilters: KeyFilter[], - isReloadOnlyOnDataUpdated = false): Observable { + ignoreDataUpdateOnIntervalTick = false): Observable { const datasource = listener.configDatasource; listener.subscriptionOptions = this.createSubscriptionOptions( datasource, @@ -99,7 +99,7 @@ export class EntityDataService { datasource.keyFilters, keyFilters, true, - isReloadOnlyOnDataUpdated); + ignoreDataUpdateOnIntervalTick); if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) { listener.dataLoaded(emptyPageData(), [], listener.configDatasourceIndex, listener.subscriptionOptions.pageLink); @@ -124,7 +124,7 @@ export class EntityDataService { keyFilters: KeyFilter[], additionalKeyFilters: KeyFilter[], isPaginatedDataSubscription: boolean, - isReloadOnlyOnDataUpdated: boolean): EntityDataSubscriptionOptions { + ignoreDataUpdateOnIntervalTick: boolean): EntityDataSubscriptionOptions { const subscriptionDataKeys: Array = []; datasource.dataKeys.forEach((dataKey) => { const subscriptionDataKey: SubscriptionDataKey = { @@ -147,7 +147,7 @@ export class EntityDataService { entityDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; } entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription; - entityDataSubscriptionOptions.isReloadOnlyOnDataUpdated = isReloadOnlyOnDataUpdated; + entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick = ignoreDataUpdateOnIntervalTick; return entityDataSubscriptionOptions; } } diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index e6cbb4108e..eff95a6f2d 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -226,7 +226,7 @@ export interface WidgetSubscriptionOptions { hasDataPageLink?: boolean; singleEntity?: boolean; warnOnPageDataOverflow?: boolean; - reloadOnlyOnDataUpdated?: boolean; + ignoreDataUpdateOnIntervalTick?: boolean; targetDeviceAliasIds?: Array; targetDeviceIds?: Array; useDashboardTimewindow?: boolean; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 7bef682d36..7f38a2f9c2 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -83,7 +83,7 @@ export class WidgetSubscription implements IWidgetSubscription { hasDataPageLink: boolean; singleEntity: boolean; warnOnPageDataOverflow: boolean; - reloadOnlyOnDataUpdated: boolean; + ignoreDataUpdateOnIntervalTick: boolean; datasourcePages: PageData[]; dataPages: PageData>[]; @@ -201,7 +201,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.hasDataPageLink = options.hasDataPageLink; this.singleEntity = options.singleEntity; this.warnOnPageDataOverflow = options.warnOnPageDataOverflow; - this.reloadOnlyOnDataUpdated = options.reloadOnlyOnDataUpdated; + this.ignoreDataUpdateOnIntervalTick = options.ignoreDataUpdateOnIntervalTick; this.datasourcePages = []; this.datasources = []; this.dataPages = []; @@ -425,7 +425,7 @@ export class WidgetSubscription implements IWidgetSubscription { } }; this.entityDataListeners.push(listener); - return this.ctx.entityDataService.prepareSubscription(listener, this.reloadOnlyOnDataUpdated); + return this.ctx.entityDataService.prepareSubscription(listener, this.ignoreDataUpdateOnIntervalTick); }); return forkJoin(resolveResultObservables).pipe( map((resolveResults) => { @@ -817,7 +817,8 @@ export class WidgetSubscription implements IWidgetSubscription { } }; this.entityDataListeners[datasourceIndex] = entityDataListener; - return this.ctx.entityDataService.subscribeForPaginatedData(entityDataListener, pageLink, keyFilters, this.reloadOnlyOnDataUpdated); + return this.ctx.entityDataService.subscribeForPaginatedData(entityDataListener, pageLink, keyFilters, + this.ignoreDataUpdateOnIntervalTick); } else { return of(null); } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index 3a9db04714..5166a845f9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -485,8 +485,8 @@ export class WidgetComponentService { if (isUndefined(result.typeParameters.warnOnPageDataOverflow)) { result.typeParameters.warnOnPageDataOverflow = true; } - if (isUndefined(result.typeParameters.reloadOnlyOnDataUpdated)) { - result.typeParameters.reloadOnlyOnDataUpdated = false; + if (isUndefined(result.typeParameters.ignoreDataUpdateOnIntervalTick)) { + result.typeParameters.ignoreDataUpdateOnIntervalTick = false; } if (isUndefined(result.typeParameters.dataKeysOptional)) { result.typeParameters.dataKeysOptional = false; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index 9d2db23d83..aa47b97c20 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -895,7 +895,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI hasDataPageLink: this.typeParameters.hasDataPageLink, singleEntity: this.typeParameters.singleEntity, warnOnPageDataOverflow: this.typeParameters.warnOnPageDataOverflow, - reloadOnlyOnDataUpdated: this.typeParameters.reloadOnlyOnDataUpdated, + ignoreDataUpdateOnIntervalTick: this.typeParameters.ignoreDataUpdateOnIntervalTick, comparisonEnabled: comparisonSettings.comparisonEnabled, timeForComparison: comparisonSettings.timeForComparison }; diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 12734ce364..cdfbd12584 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -154,7 +154,7 @@ export interface WidgetTypeParameters { hasDataPageLink?: boolean; singleEntity?: boolean; warnOnPageDataOverflow?: boolean; - reloadOnlyOnDataUpdated?: boolean; + ignoreDataUpdateOnIntervalTick?: boolean; } export interface WidgetControllerDescriptor {