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 {