From 08983bddf9dfaffe9034f8d78c84115e988ff7da Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 26 Jun 2020 11:00:00 +0300 Subject: [PATCH] UI: Code cleanup. Entities table widget: Additional sorting of resulting page data. --- ui-ngx/src/app/core/api/alias-controller.ts | 1 - .../app/core/api/datasource-subcription.ts | 666 ------------------ ui-ngx/src/app/core/api/datasource.service.ts | 109 --- .../app/core/api/entity-data-subscription.ts | 33 +- .../src/app/core/api/entity-data.service.ts | 11 +- ui-ngx/src/app/core/api/public-api.ts | 4 +- ui-ngx/src/app/core/api/widget-api.models.ts | 4 - .../src/app/core/api/widget-subscription.ts | 272 ------- ui-ngx/src/app/core/http/entity.service.ts | 215 ------ ...aliases-entity-select-panel.component.html | 9 - .../aliases-entity-select-panel.component.ts | 5 - .../alias/aliases-entity-select.component.ts | 3 +- .../lib/entities-table-widget.component.ts | 41 +- .../components/widget/widget.component.ts | 2 - ui-ngx/src/app/shared/models/alias.models.ts | 1 - ui-ngx/src/app/shared/models/entity.models.ts | 3 - .../src/app/shared/models/page/page-link.ts | 49 +- .../app/shared/models/query/query.models.ts | 8 + 18 files changed, 89 insertions(+), 1347 deletions(-) delete mode 100644 ui-ngx/src/app/core/api/datasource-subcription.ts delete mode 100644 ui-ngx/src/app/core/api/datasource.service.ts diff --git a/ui-ngx/src/app/core/api/alias-controller.ts b/ui-ngx/src/app/core/api/alias-controller.ts index 717dd9d34a..124844a885 100644 --- a/ui-ngx/src/app/core/api/alias-controller.ts +++ b/ui-ngx/src/app/core/api/alias-controller.ts @@ -24,7 +24,6 @@ import { EntityAliases } from '@shared/models/alias.models'; import { EntityInfo } from '@shared/models/entity.models'; import { map, mergeMap } from 'rxjs/operators'; import { - createDefaultEntityDataPageLink, defaultEntityDataPageLink, singleEntityDataPageLink, updateDatasourceFromEntityInfo } from '@shared/models/query/query.models'; diff --git a/ui-ngx/src/app/core/api/datasource-subcription.ts b/ui-ngx/src/app/core/api/datasource-subcription.ts deleted file mode 100644 index c3d182595d..0000000000 --- a/ui-ngx/src/app/core/api/datasource-subcription.ts +++ /dev/null @@ -1,666 +0,0 @@ -/// -/// Copyright © 2016-2020 The Thingsboard Authors -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// - -import { DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models'; -import { - AttributesSubscriptionCmd, - DataKeyType, - GetHistoryCmd, - SubscriptionData, - SubscriptionDataHolder, - SubscriptionUpdateMsg, - TelemetryService, - TelemetrySubscriber, - TimeseriesSubscriptionCmd -} from '@shared/models/telemetry/telemetry.models'; -import { DatasourceListener } from './datasource.service'; -import { AggregationType, SubscriptionTimewindow, YEAR } from '@shared/models/time/time.models'; -import { deepClone, isDefinedAndNotNull, isObject, objectHashCode } from '@core/utils'; -import { UtilsService } from '@core/services/utils.service'; -import { EntityType } from '@shared/models/entity-type.models'; -import { DataAggregator } from '@core/api/data-aggregator'; -import Timeout = NodeJS.Timeout; - -declare type DataKeyFunction = (time: number, prevValue: any) => any; - -declare type DataKeyPostFunction = (time: number, value: any, prevValue: any, timePrev: number, prevOrigValue: any) => any; - -export interface SubscriptionDataKey { - name: string; - type: DataKeyType; - funcBody: string; - func?: DataKeyFunction; - postFuncBody: string; - postFunc?: DataKeyPostFunction; - index?: number; - key?: string; - lastUpdateTime?: number; -} - -export interface DatasourceSubscriptionOptions { - datasourceType: DatasourceType; - dataKeys: Array; - type: widgetType; - entityType?: EntityType; - entityId?: string; - subscriptionTimewindow?: SubscriptionTimewindow; -} - -export class DatasourceSubscription { - - private listeners: Array = []; - private datasourceType: DatasourceType = this.datasourceSubscriptionOptions.datasourceType; - - private history = this.datasourceSubscriptionOptions.subscriptionTimewindow && - isObject(this.datasourceSubscriptionOptions.subscriptionTimewindow.fixedWindow); - - private realtime = this.datasourceSubscriptionOptions.subscriptionTimewindow && - isDefinedAndNotNull(this.datasourceSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs); - - private subscribers = new Array(); - - private dataAggregator: DataAggregator; - - private dataKeys: {[key: string]: Array | SubscriptionDataKey} = {}; - private datasourceData: {[key: string]: DataSetHolder} = {}; - private datasourceOrigData: {[key: string]: DataSetHolder} = {}; - - private frequency: number; - private tickScheduledTime = 0; - private tickElapsed = 0; - private timer: Timeout; - - constructor(private datasourceSubscriptionOptions: DatasourceSubscriptionOptions, - private telemetryService: TelemetryService, - private utils: UtilsService) { - this.initializeSubscription(); - } - - private initializeSubscription() { - for (let i = 0; i < this.datasourceSubscriptionOptions.dataKeys.length; i++) { - const dataKey = deepClone(this.datasourceSubscriptionOptions.dataKeys[i]); - dataKey.index = i; - if (this.datasourceType === DatasourceType.function) { - if (!dataKey.func) { - dataKey.func = new Function('time', 'prevValue', dataKey.funcBody) as DataKeyFunction; - } - } else { - if (dataKey.postFuncBody && !dataKey.postFunc) { - dataKey.postFunc = new Function('time', 'value', 'prevValue', 'timePrev', 'prevOrigValue', - dataKey.postFuncBody) as DataKeyPostFunction; - } - } - let key: string; - if (this.datasourceType === DatasourceType.entity || this.datasourceSubscriptionOptions.type === widgetType.timeseries) { - if (this.datasourceType === DatasourceType.function) { - key = `${dataKey.name}_${dataKey.index}_${dataKey.type}`; - } else { - key = `${dataKey.name}_${dataKey.type}`; - } - let dataKeysList = this.dataKeys[key] as Array; - if (!dataKeysList) { - dataKeysList = []; - this.dataKeys[key] = dataKeysList; - } - const index = dataKeysList.push(dataKey) - 1; - this.datasourceData[key + '_' + index] = { - data: [] - }; - } else { - key = String(objectHashCode(dataKey)); - this.datasourceData[key] = { - data: [] - }; - this.dataKeys[key] = dataKey; - } - dataKey.key = key; - } - this.datasourceOrigData = deepClone(this.datasourceData); - if (this.datasourceType === DatasourceType.function) { - this.frequency = 1000; - if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { - this.frequency = Math.min(this.datasourceSubscriptionOptions.subscriptionTimewindow.aggregation.interval, 5000); - } - } - } - - public addListener(listener: DatasourceListener) { - this.listeners.push(listener); - if (this.history) { - this.start(); - } - } - - public hasListeners(): boolean { - return this.listeners.length > 0; - } - - public removeListener(listener: DatasourceListener) { - this.listeners.splice(this.listeners.indexOf(listener), 1); - } - - public syncListener(listener: DatasourceListener) { - let key: string; - let dataKey: SubscriptionDataKey; - if (this.datasourceType === DatasourceType.entity || this.datasourceSubscriptionOptions.type === widgetType.timeseries) { - for (key of Object.keys(this.dataKeys)) { - const dataKeysList = this.dataKeys[key] as Array; - for (let i = 0; i < dataKeysList.length; i++) { - dataKey = dataKeysList[i]; - const datasourceKey = `${key}_${i}`; - listener.dataUpdated(this.datasourceData[datasourceKey], - listener.datasourceIndex, - dataKey.index, false); - } - } - } else { - for (key of Object.keys(this.dataKeys)) { - dataKey = this.dataKeys[key] as SubscriptionDataKey; - listener.dataUpdated(this.datasourceData[key], - listener.datasourceIndex, - dataKey.index, false); - } - } - } - - public unsubscribe() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - if (this.datasourceType === DatasourceType.entity) { - this.subscribers.forEach( - (subscriber) => { - subscriber.unsubscribe(); - } - ); - this.subscribers.length = 0; - } - if (this.dataAggregator) { - this.dataAggregator.destroy(); - this.dataAggregator = null; - } - } - - public start() { - if (this.history && !this.hasListeners()) { - return; - } - let subsTw = this.datasourceSubscriptionOptions.subscriptionTimewindow; - const tsKeyNames: string[] = []; - const attrKeyNames: string[] = []; - let dataKey: SubscriptionDataKey; - if (this.datasourceType === DatasourceType.entity) { - - let tsKeys = ''; - let attrKeys = ''; - - for (const key of Object.keys(this.dataKeys)) { - const dataKeysList = this.dataKeys[key] as Array; - dataKey = dataKeysList[0]; - if (dataKey.type === DataKeyType.timeseries) { - tsKeyNames.push(dataKey.name); - } else if (dataKey.type === DataKeyType.attribute) { - attrKeyNames.push(dataKey.name); - } - } - tsKeys = tsKeyNames.join(','); - attrKeys = attrKeyNames.join(','); - if (tsKeys.length > 0) { - if (this.history) { - const historyCommand = new GetHistoryCmd(); - historyCommand.entityType = this.datasourceSubscriptionOptions.entityType; - historyCommand.entityId = this.datasourceSubscriptionOptions.entityId; - historyCommand.keys = tsKeys; - historyCommand.startTs = subsTw.fixedWindow.startTimeMs; - historyCommand.endTs = subsTw.fixedWindow.endTimeMs; - historyCommand.interval = subsTw.aggregation.interval; - historyCommand.limit = subsTw.aggregation.limit; - historyCommand.agg = subsTw.aggregation.type; - - const subscriber = new TelemetrySubscriber(this.telemetryService); - subscriber.subscriptionCommands.push(historyCommand); - - let firstStateHistoryCommand: GetHistoryCmd; - if (subsTw.aggregation.stateData) { - firstStateHistoryCommand = this.createFirstStateHistoryCommand(subsTw.fixedWindow.startTimeMs, tsKeys); - subscriber.subscriptionCommands.push(firstStateHistoryCommand); - } - let data: SubscriptionUpdateMsg; - let firstStateData: SubscriptionUpdateMsg; - - subscriber.data$.subscribe( - (subscriptionUpdate) => { - if (subsTw.aggregation.stateData && firstStateHistoryCommand - && firstStateHistoryCommand.cmdId === subscriptionUpdate.subscriptionId) { - if (data) { - this.onStateHistoryData(subscriptionUpdate, data, subsTw.aggregation.limit, - subsTw.fixedWindow.startTimeMs, subsTw.fixedWindow.endTimeMs, - (newData) => { - this.onData(newData.data, DataKeyType.timeseries, true); - } - ); - } else { - firstStateData = data; - } - } else { - if (subsTw.aggregation.stateData) { - if (firstStateData) { - this.onStateHistoryData(firstStateData, subscriptionUpdate, subsTw.aggregation.limit, - subsTw.fixedWindow.startTimeMs, subsTw.fixedWindow.endTimeMs, - (newData) => { - this.onData(newData.data, DataKeyType.timeseries, true); - }); - } else { - data = subscriptionUpdate; - } - } else { - for (const key of Object.keys(subscriptionUpdate.data)) { - const keyData = subscriptionUpdate.data[key]; - keyData.sort((set1, set2) => set1[0] - set2[0]); - } - this.onData(subscriptionUpdate.data, DataKeyType.timeseries, true); - } - } - } - ); - subscriber.subscribe(); - this.subscribers.push(subscriber); - } else { - const subscriptionCommand = new TimeseriesSubscriptionCmd(); - subscriptionCommand.entityType = this.datasourceSubscriptionOptions.entityType; - subscriptionCommand.entityId = this.datasourceSubscriptionOptions.entityId; - subscriptionCommand.keys = tsKeys; - - const subscriber = new TelemetrySubscriber(this.telemetryService); - subscriber.subscriptionCommands.push(subscriptionCommand); - - if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { - this.updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw); - - let firstStateSubscriptionCommand: GetHistoryCmd; - if (subsTw.aggregation.stateData) { - firstStateSubscriptionCommand = this.createFirstStateHistoryCommand(subsTw.startTs, tsKeys); - subscriber.subscriptionCommands.push(firstStateSubscriptionCommand); - } - this.dataAggregator = this.createRealtimeDataAggregator(subsTw, tsKeyNames, DataKeyType.timeseries); - - let data: SubscriptionUpdateMsg; - let firstStateData: SubscriptionUpdateMsg; - let stateDataReceived: boolean; - - subscriber.data$.subscribe( - (subscriptionUpdate) => { - if (subsTw.aggregation.stateData && - firstStateSubscriptionCommand && firstStateSubscriptionCommand.cmdId === subscriptionUpdate.subscriptionId) { - if (data) { - this.onStateHistoryData(subscriptionUpdate, data, subsTw.aggregation.limit, - subsTw.startTs, subsTw.startTs + subsTw.aggregation.timeWindow, - (newData) => { - this.dataAggregator.onData(newData, false, false, true); - }); - stateDataReceived = true; - } else { - firstStateData = subscriptionUpdate; - } - } else { - if (subsTw.aggregation.stateData && !stateDataReceived) { - if (firstStateData) { - this.onStateHistoryData(firstStateData, subscriptionUpdate, subsTw.aggregation.limit, - subsTw.startTs, subsTw.startTs + subsTw.aggregation.timeWindow, - (newData) => { - this.dataAggregator.onData(newData, false, false, true); - }); - stateDataReceived = true; - } else { - data = subscriptionUpdate; - } - } else { - this.dataAggregator.onData(subscriptionUpdate, false, false, true); - } - } - } - ); - subscriber.reconnect$.subscribe(() => { - let newSubsTw: SubscriptionTimewindow = null; - this.listeners.forEach((listener) => { - if (!newSubsTw) { - newSubsTw = listener.updateRealtimeSubscription(); - } else { - listener.setRealtimeSubscription(newSubsTw); - } - }); - subsTw = newSubsTw; - firstStateData = null; - data = null; - stateDataReceived = false; - this.updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw); - if (subsTw.aggregation.stateData) { - this.updateFirstStateHistoryCommand(firstStateSubscriptionCommand, subsTw.startTs); - } - this.dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); - }); - } else { - subscriber.data$.subscribe( - (subscriptionUpdate) => { - if (subscriptionUpdate.data) { - this.onData(subscriptionUpdate.data, DataKeyType.timeseries, true); - } - } - ); - } - - subscriber.subscribe(); - this.subscribers.push(subscriber); - - } - } - - if (attrKeys.length) { - const attrsSubscriptionCommand = new AttributesSubscriptionCmd(); - attrsSubscriptionCommand.entityType = this.datasourceSubscriptionOptions.entityType; - attrsSubscriptionCommand.entityId = this.datasourceSubscriptionOptions.entityId; - attrsSubscriptionCommand.keys = attrKeys; - - const subscriber = new TelemetrySubscriber(this.telemetryService); - subscriber.subscriptionCommands.push(attrsSubscriptionCommand); - subscriber.data$.subscribe( - (subscriptionUpdate) => { - if (subscriptionUpdate.data) { - this.onData(subscriptionUpdate.data, DataKeyType.attribute, true); - } - } - ); - - subscriber.subscribe(); - this.subscribers.push(subscriber); - } - } else if (this.datasourceType === DatasourceType.function) { - if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { - for (const key of Object.keys(this.dataKeys)) { - const dataKeysList = this.dataKeys[key] as Array; - dataKeysList.forEach((subscriptionDataKey) => { - tsKeyNames.push(`${subscriptionDataKey.name}_${subscriptionDataKey.index}`); - }); - } - this.dataAggregator = this.createRealtimeDataAggregator(subsTw, tsKeyNames, DataKeyType.function); - } - this.tickScheduledTime = this.utils.currentPerfTime(); - if (this.history) { - this.onTick(true); - } else { - this.timer = setTimeout(this.onTick.bind(this, true), 0); - } - } - } - - private createFirstStateHistoryCommand(startTs: number, tsKeys: string): GetHistoryCmd { - const command = new GetHistoryCmd(); - command.entityType = this.datasourceSubscriptionOptions.entityType; - command.entityId = this.datasourceSubscriptionOptions.entityId; - command.keys = tsKeys; - command.startTs = startTs - YEAR; - command.endTs = startTs; - command.interval = 1000; - command.limit = 1; - command.agg = AggregationType.NONE; - return command; - } - - private updateFirstStateHistoryCommand(stateHistoryCommand: GetHistoryCmd, startTs: number) { - stateHistoryCommand.startTs = startTs - YEAR; - stateHistoryCommand.endTs = startTs; - } - - private onStateHistoryData(firstStateData: SubscriptionUpdateMsg, data: SubscriptionUpdateMsg, - limit: number, startTs: number, endTs: number, onData: (data: SubscriptionUpdateMsg) => void) { - for (const key of Object.keys(data.data)) { - const keyData = data.data[key]; - keyData.sort((set1, set2) => set1[0] - set2[0]); - if (keyData.length < limit) { - let firstStateKeyData = firstStateData.data[key]; - if (firstStateKeyData.length) { - const firstStateDataTsKv = firstStateKeyData[0]; - firstStateDataTsKv[0] = startTs; - firstStateKeyData = [ - [ startTs, firstStateKeyData[0][1] ] - ]; - keyData.unshift(firstStateDataTsKv); - } - } - if (keyData.length) { - const lastTsKv = deepClone(keyData[keyData.length - 1]); - lastTsKv[0] = endTs; - keyData.push(lastTsKv); - } - } - onData(data); - } - - private createRealtimeDataAggregator(subsTw: SubscriptionTimewindow, - tsKeyNames: Array, dataKeyType: DataKeyType): DataAggregator { - return new DataAggregator( - (data, detectChanges) => { - this.onData(data, dataKeyType, detectChanges); - }, - tsKeyNames, - subsTw.startTs, - subsTw.aggregation.limit, - subsTw.aggregation.type, - subsTw.aggregation.timeWindow, - subsTw.aggregation.interval, - subsTw.aggregation.stateData, - this.utils - ); - } - - private updateRealtimeSubscriptionCommand(subscriptionCommand: TimeseriesSubscriptionCmd, subsTw: SubscriptionTimewindow) { - subscriptionCommand.startTs = subsTw.startTs; - subscriptionCommand.timeWindow = subsTw.aggregation.timeWindow; - subscriptionCommand.interval = subsTw.aggregation.interval; - subscriptionCommand.limit = subsTw.aggregation.limit; - subscriptionCommand.agg = subsTw.aggregation.type; - } - - private generateSeries(dataKey: SubscriptionDataKey, index: number, startTime: number, endTime: number): [number, any][] { - const data: [number, any][] = []; - let prevSeries: [number, any]; - const datasourceDataKey = `${dataKey.key}_${index}`; - const datasourceKeyData = this.datasourceData[datasourceDataKey].data; - if (datasourceKeyData.length > 0) { - prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; - } else { - prevSeries = [0, 0]; - } - for (let time = startTime; time <= endTime && (this.timer || this.history); time += this.frequency) { - const value = dataKey.func(time, prevSeries[1]); - const series: [number, any] = [time, value]; - data.push(series); - prevSeries = series; - } - if (data.length > 0) { - dataKey.lastUpdateTime = data[data.length - 1][0]; - } - return data; - } - - private generateLatest(dataKey: SubscriptionDataKey, detectChanges: boolean) { - let prevSeries: [number, any]; - const datasourceKeyData = this.datasourceData[dataKey.key].data; - if (datasourceKeyData.length > 0) { - prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; - } else { - prevSeries = [0, 0]; - } - const time = Date.now(); - const value = dataKey.func(time, prevSeries[1]); - const series: [number, any] = [time, value]; - this.datasourceData[dataKey.key].data = [series]; - this.listeners.forEach( - (listener) => { - listener.dataUpdated(this.datasourceData[dataKey.key], - listener.datasourceIndex, - dataKey.index, detectChanges); - } - ); - } - - private onTick(detectChanges: boolean) { - const now = this.utils.currentPerfTime(); - this.tickElapsed += now - this.tickScheduledTime; - this.tickScheduledTime = now; - - if (this.timer) { - clearTimeout(this.timer); - } - let key: string; - if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { - let startTime: number; - let endTime: number; - let delta: number; - const generatedData: SubscriptionDataHolder = { - data: {} - }; - if (!this.history) { - delta = Math.floor(this.tickElapsed / this.frequency); - } - const deltaElapsed = this.history ? this.frequency : delta * this.frequency; - this.tickElapsed = this.tickElapsed - deltaElapsed; - for (key of Object.keys(this.dataKeys)) { - const dataKeyList = this.dataKeys[key] as Array; - for (let index = 0; index < dataKeyList.length && (this.timer || this.history); index ++) { - const dataKey = dataKeyList[index]; - if (!startTime) { - if (this.realtime) { - if (dataKey.lastUpdateTime) { - startTime = dataKey.lastUpdateTime + this.frequency; - endTime = dataKey.lastUpdateTime + deltaElapsed; - } else { - startTime = this.datasourceSubscriptionOptions.subscriptionTimewindow.startTs; - endTime = startTime + this.datasourceSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; - if (this.datasourceSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { - const time = endTime - this.frequency * this.datasourceSubscriptionOptions.subscriptionTimewindow.aggregation.limit; - startTime = Math.max(time, startTime); - } - } - } else { - startTime = this.datasourceSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs; - endTime = this.datasourceSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs; - } - } - const data = this.generateSeries(dataKey, index, startTime, endTime); - generatedData.data[`${dataKey.name}_${dataKey.index}`] = data; - } - } - if (this.dataAggregator) { - this.dataAggregator.onData(generatedData, true, this.history, detectChanges); - } - } else if (this.datasourceSubscriptionOptions.type === widgetType.latest) { - for (key of Object.keys(this.dataKeys)) { - this.generateLatest(this.dataKeys[key] as SubscriptionDataKey, detectChanges); - } - } - - if (!this.history) { - this.timer = setTimeout(this.onTick.bind(this, true), this.frequency); - } - } - - private onData(sourceData: SubscriptionData, type: DataKeyType, detectChanges: boolean) { - for (const keyName of Object.keys(sourceData)) { - const keyData = sourceData[keyName]; - const key = `${keyName}_${type}`; - const dataKeyList = this.dataKeys[key] as Array; - for (let keyIndex = 0; dataKeyList && keyIndex < dataKeyList.length; keyIndex++) { - const datasourceKey = `${key}_${keyIndex}`; - if (this.datasourceData[datasourceKey].data) { - const dataKey = dataKeyList[keyIndex]; - const data: DataSet = []; - let prevSeries: [number, any]; - let prevOrigSeries: [number, any]; - let datasourceKeyData: DataSet; - let datasourceOrigKeyData: DataSet; - let update = false; - if (this.realtime) { - datasourceKeyData = []; - datasourceOrigKeyData = []; - } else { - datasourceKeyData = this.datasourceData[datasourceKey].data; - datasourceOrigKeyData = this.datasourceOrigData[datasourceKey].data; - } - if (datasourceKeyData.length > 0) { - prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; - prevOrigSeries = datasourceOrigKeyData[datasourceOrigKeyData.length - 1]; - } else { - prevSeries = [0, 0]; - prevOrigSeries = [0, 0]; - } - this.datasourceOrigData[datasourceKey].data = []; - if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { - keyData.forEach((keySeries) => { - let series = keySeries; - const time = series[0]; - this.datasourceOrigData[datasourceKey].data.push(series); - let value = this.convertValue(series[1]); - if (dataKey.postFunc) { - value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); - } - prevOrigSeries = series; - series = [time, value]; - data.push(series); - prevSeries = series; - }); - update = true; - } else if (this.datasourceSubscriptionOptions.type === widgetType.latest) { - if (keyData.length > 0) { - let series = keyData[0]; - const time = series[0]; - this.datasourceOrigData[datasourceKey].data.push(series); - let value = this.convertValue(series[1]); - if (dataKey.postFunc) { - value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); - } - series = [time, value]; - data.push(series); - } - update = true; - } - if (update) { - this.datasourceData[datasourceKey].data = data; - this.listeners.forEach((listener) => { - listener.dataUpdated(this.datasourceData[datasourceKey], - listener.datasourceIndex, - dataKey.index, detectChanges); - }); - } - } - } - } - } - - private isNumeric(val: any): boolean { - return (val - parseFloat( val ) + 1) >= 0; - } - - private convertValue(val: string): any { - if (val && this.isNumeric(val)) { - return Number(val); - } else { - return val; - } - } - -} diff --git a/ui-ngx/src/app/core/api/datasource.service.ts b/ui-ngx/src/app/core/api/datasource.service.ts deleted file mode 100644 index b45fc431e0..0000000000 --- a/ui-ngx/src/app/core/api/datasource.service.ts +++ /dev/null @@ -1,109 +0,0 @@ -/// -/// Copyright © 2016-2020 The Thingsboard Authors -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// - -import { Injectable } from '@angular/core'; -import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; -import { UtilsService } from '@core/services/utils.service'; -import { EntityType } from '@app/shared/models/entity-type.models'; -import { DataSetHolder, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; -import { SubscriptionTimewindow } from '@shared/models/time/time.models'; -import { - DatasourceSubscription, - DatasourceSubscriptionOptions, - SubscriptionDataKey -} from '@core/api/datasource-subcription'; -import { deepClone, objectHashCode } from '@core/utils'; - -export interface DatasourceListener { - subscriptionType: widgetType; - subscriptionTimewindow: SubscriptionTimewindow; - datasource: Datasource; - entityType: EntityType; - entityId: string; - datasourceIndex: number; - dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; - updateRealtimeSubscription: () => SubscriptionTimewindow; - setRealtimeSubscription: (subscriptionTimewindow: SubscriptionTimewindow) => void; - datasourceSubscriptionKey?: number; -} - -@Injectable({ - providedIn: 'root' -}) -export class DatasourceService { - - private subscriptions: {[datasourceSubscriptionKey: string]: DatasourceSubscription} = {}; - - constructor(private telemetryService: TelemetryWebsocketService, - private utils: UtilsService) {} - - public subscribeToDatasource(listener: DatasourceListener) { - const datasource = listener.datasource; - if (datasource.type === DatasourceType.entity && (!listener.entityId || !listener.entityType)) { - return; - } - const subscriptionDataKeys: Array = []; - datasource.dataKeys.forEach((dataKey) => { - const subscriptionDataKey: SubscriptionDataKey = { - name: dataKey.name, - type: dataKey.type, - funcBody: dataKey.funcBody, - postFuncBody: dataKey.postFuncBody - }; - subscriptionDataKeys.push(subscriptionDataKey); - }); - - const datasourceSubscriptionOptions: DatasourceSubscriptionOptions = { - datasourceType: datasource.type, - dataKeys: subscriptionDataKeys, - type: listener.subscriptionType - }; - - if (listener.subscriptionType === widgetType.timeseries) { - datasourceSubscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); - } - if (datasourceSubscriptionOptions.datasourceType === DatasourceType.entity) { - datasourceSubscriptionOptions.entityType = listener.entityType; - datasourceSubscriptionOptions.entityId = listener.entityId; - } - listener.datasourceSubscriptionKey = objectHashCode(datasourceSubscriptionOptions); - let subscription: DatasourceSubscription; - if (this.subscriptions[listener.datasourceSubscriptionKey]) { - subscription = this.subscriptions[listener.datasourceSubscriptionKey]; - subscription.syncListener(listener); - } else { - subscription = new DatasourceSubscription(datasourceSubscriptionOptions, - this.telemetryService, this.utils); - this.subscriptions[listener.datasourceSubscriptionKey] = subscription; - subscription.start(); - } - subscription.addListener(listener); - } - - public unsubscribeFromDatasource(listener: DatasourceListener) { - if (listener.datasourceSubscriptionKey) { - const subscription = this.subscriptions[listener.datasourceSubscriptionKey]; - if (subscription) { - subscription.removeListener(listener); - if (!subscription.hasListeners()) { - subscription.unsubscribe(); - delete this.subscriptions[listener.datasourceSubscriptionKey]; - } - } - listener.datasourceSubscriptionKey = null; - } - } -} 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 69151a5112..f51e3c04f2 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -16,14 +16,13 @@ import { DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models'; import { AggregationType, SubscriptionTimewindow } from '@shared/models/time/time.models'; -import { SubscriptionDataKey } from '@core/api/datasource-subcription'; import { EntityData, EntityDataPageLink, EntityFilter, EntityKey, EntityKeyType, - entityKeyTypeToDataKeyType, + entityKeyTypeToDataKeyType, entityPageDataChanged, KeyFilter, TsValue } from '@shared/models/query/query.models'; @@ -37,7 +36,7 @@ import { } from '@shared/models/telemetry/telemetry.models'; import { UtilsService } from '@core/services/utils.service'; import { EntityDataListener, EntityDataLoadResult } from '@core/api/entity-data.service'; -import { deepClone, isDefinedAndNotNull, isEqual, isObject, objectHashCode } from '@core/utils'; +import { deepClone, isDefinedAndNotNull, isObject, objectHashCode } from '@core/utils'; import { PageData } from '@shared/models/page/page-data'; import { DataAggregator } from '@core/api/data-aggregator'; import { NULL_UUID } from '@shared/models/id/has-uuid'; @@ -45,6 +44,22 @@ import { EntityType } from '@shared/models/entity-type.models'; import { Observable, of, ReplaySubject, Subject } from 'rxjs'; import Timeout = NodeJS.Timeout; +declare type DataKeyFunction = (time: number, prevValue: any) => any; +declare type DataKeyPostFunction = (time: number, value: any, prevValue: any, timePrev: number, prevOrigValue: any) => any; +declare type DataUpdatedCb = (data: DataSetHolder, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; + +export interface SubscriptionDataKey { + name: string; + type: DataKeyType; + funcBody: string; + func?: DataKeyFunction; + postFuncBody: string; + postFunc?: DataKeyPostFunction; + index?: number; + key?: string; + lastUpdateTime?: number; +} + export interface EntityDataSubscriptionOptions { datasourceType: DatasourceType; dataKeys: Array; @@ -56,10 +71,6 @@ export interface EntityDataSubscriptionOptions { subscriptionTimewindow?: SubscriptionTimewindow; } -declare type DataKeyFunction = (time: number, prevValue: any) => any; -declare type DataKeyPostFunction = (time: number, value: any, prevValue: any, timePrev: number, prevOrigValue: any) => any; -declare type DataUpdatedCb = (data: DataSetHolder, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; - export class EntityDataSubscription { private datasourceType: DatasourceType = this.entityDataSubscriptionOptions.datasourceType; @@ -421,16 +432,10 @@ export class EntityDataSubscription { } } - private pageDataChanged(prevPageData: PageData, nextPageData: PageData) { - const prevIds = prevPageData.data.map((entityData) => entityData.entityId.id); - const nextIds = nextPageData.data.map((entityData) => entityData.entityId.id); - return !isEqual(prevIds, nextIds); - } - private onPageData(pageData: PageData) { const isInitialData = !this.pageData; if (!isInitialData && !this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { - if (this.pageDataChanged(this.pageData, pageData)) { + if (entityPageDataChanged(this.pageData, pageData)) { if (this.listener.initialPageDataChanged) { this.listener.initialPageDataChanged(pageData); } 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 a7b4bc2911..2a2c6dd44a 100644 --- a/ui-ngx/src/app/core/api/entity-data.service.ts +++ b/ui-ngx/src/app/core/api/entity-data.service.ts @@ -16,14 +16,17 @@ import { DataSetHolder, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; import { SubscriptionTimewindow } from '@shared/models/time/time.models'; -import { EntityData, EntityDataPageLink, EntityFilter, KeyFilter } from '@shared/models/query/query.models'; +import { EntityData, EntityDataPageLink, KeyFilter } from '@shared/models/query/query.models'; import { PageData } from '@shared/models/page/page-data'; import { Injectable } from '@angular/core'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; import { UtilsService } from '@core/services/utils.service'; -import { SubscriptionDataKey } from '@core/api/datasource-subcription'; -import { deepClone, objectHashCode } from '@core/utils'; -import { EntityDataSubscription, EntityDataSubscriptionOptions } from '@core/api/entity-data-subscription'; +import { deepClone } from '@core/utils'; +import { + EntityDataSubscription, + EntityDataSubscriptionOptions, + SubscriptionDataKey +} from '@core/api/entity-data-subscription'; import { Observable, of } from 'rxjs'; export interface EntityDataListener { diff --git a/ui-ngx/src/app/core/api/public-api.ts b/ui-ngx/src/app/core/api/public-api.ts index 3d292cfd40..a2bf6769b2 100644 --- a/ui-ngx/src/app/core/api/public-api.ts +++ b/ui-ngx/src/app/core/api/public-api.ts @@ -16,7 +16,7 @@ export * from './alias-controller'; export * from './data-aggregator'; -export * from './datasource.service'; -export * from './datasource-subcription'; +export * from './entity-data.service'; +export * from './entity-data-subscription'; export * from './widget-api.models'; export * from './widget-subscription'; 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 7ce7b00690..543d7dafe3 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -35,7 +35,6 @@ import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; import { EntityType } from '@shared/models/entity-type.models'; import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models'; import { HttpErrorResponse } from '@angular/common/http'; -import { DatasourceService } from '@core/api/datasource.service'; import { RafService } from '@core/services/raf.service'; import { EntityAliases } from '@shared/models/alias.models'; import { EntityInfo } from '@app/shared/models/entity.models'; @@ -82,8 +81,6 @@ export interface AliasInfo { stateEntity?: boolean; entityFilter?: EntityFilter; currentEntity?: EntityInfo; - selectedId?: string; - resolvedEntities?: Array; entityParamName?: string; resolveMultiple?: boolean; } @@ -176,7 +173,6 @@ export class WidgetSubscriptionContext { deviceService: DeviceService; alarmService: AlarmService; translate: TranslateService; - // datasourceService: DatasourceService; entityDataService: EntityDataService; utils: UtilsService; raf: RafService; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index df9f232bfd..531d5b498d 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -57,7 +57,6 @@ import { EntityData, EntityDataPageLink, entityDataToEntityInfo, - EntityKeyType, KeyFilter, updateDatasourceFromEntityInfo } from '@shared/models/query/query.models'; import { map } from 'rxjs/operators'; @@ -87,7 +86,6 @@ export class WidgetSubscription implements IWidgetSubscription { data: Array; datasources: Array; - // datasourceListeners: Array; hiddenData: Array; legendData: LegendData; legendConfig: LegendConfig; @@ -215,12 +213,10 @@ export class WidgetSubscription implements IWidgetSubscription { this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {}); this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); - // this.datasources = this.ctx.utils.validateDatasources(options.datasources); this.configuredDatasources = this.ctx.utils.validateDatasources(options.datasources); this.entityDataListeners = []; this.hasDataPageLink = options.hasDataPageLink; this.singleEntity = options.singleEntity; - // this.datasourceListeners = []; this.datasourcePages = []; this.datasources = []; this.dataPages = []; @@ -457,125 +453,6 @@ export class WidgetSubscription implements IWidgetSubscription { ); } -/* private initDataSubscriptionOld(): Observable { - const initDataSubscriptionSubject = new ReplaySubject(1); - this.loadStDiff().subscribe(() => { - if (!this.ctx.aliasController) { - this.hasResolvedData = true; - this.configureData(); - initDataSubscriptionSubject.next(); - initDataSubscriptionSubject.complete(); - } else { - this.ctx.aliasController.resolveDatasources(this.datasources).subscribe( - (datasources) => { - this.datasources = datasources; - if (datasources && datasources.length) { - this.hasResolvedData = true; - } - this.configureData(); - initDataSubscriptionSubject.next(); - initDataSubscriptionSubject.complete(); - }, - (err) => { - this.notifyDataLoaded(); - initDataSubscriptionSubject.error(err); - } - ); - } - }); - return initDataSubscriptionSubject.asObservable(); - } */ - - /* private configureData() { - const additionalDatasources: Datasource[] = []; - let dataIndex = 0; - let additionalKeysNumber = 0; - this.datasources.forEach((datasource) => { - const additionalDataKeys: DataKey[] = []; - let datasourceAdditionalKeysNumber = 0; - datasource.dataKeys.forEach((dataKey) => { - dataKey.hidden = dataKey.settings.hideDataByDefault ? true : false; - dataKey.inLegend = dataKey.settings.removeFromLegend ? false : true; - dataKey.pattern = dataKey.label; - if (this.comparisonEnabled && dataKey.settings.comparisonSettings && dataKey.settings.comparisonSettings.showValuesForComparison) { - datasourceAdditionalKeysNumber++; - additionalKeysNumber++; - const additionalDataKey = this.ctx.utils.createAdditionalDataKey(dataKey, datasource, - this.timeForComparison, this.datasources, additionalKeysNumber); - dataKey.settings.comparisonSettings.color = additionalDataKey.color; - additionalDataKeys.push(additionalDataKey); - } - const datasourceData: DatasourceData = { - datasource, - dataKey, - data: [] - }; - if (dataKey.type === DataKeyType.entityField && datasource.entity) { - const propName = entityFields[dataKey.name] ? entityFields[dataKey.name].value : dataKey.name; - if (datasource.entity[propName]) { - datasourceData.data.push([Date.now(), datasource.entity[propName]]); - } - } - this.data.push(datasourceData); - this.hiddenData.push({data: []}); - if (this.displayLegend) { - const legendKey: LegendKey = { - dataKey, - dataIndex: dataIndex++ - }; - this.legendData.keys.push(legendKey); - const legendKeyData: LegendKeyData = { - min: null, - max: null, - avg: null, - total: null, - hidden: false - }; - this.legendData.data.push(legendKeyData); - } - }); - if (datasourceAdditionalKeysNumber > 0) { - const additionalDatasource: Datasource = deepClone(datasource); - additionalDatasource.dataKeys = additionalDataKeys; - additionalDatasource.isAdditional = true; - additionalDatasources.push(additionalDatasource); - } - }); - - additionalDatasources.forEach((additionalDatasource) => { - additionalDatasource.dataKeys.forEach((additionalDataKey) => { - const additionalDatasourceData: DatasourceData = { - datasource: additionalDatasource, - dataKey: additionalDataKey, - data: [] - }; - this.data.push(additionalDatasourceData); - this.hiddenData.push({data: []}); - if (this.displayLegend) { - const additionalLegendKey: LegendKey = { - dataKey: additionalDataKey, - dataIndex: dataIndex++ - }; - this.legendData.keys.push(additionalLegendKey); - const additionalLegendKeyData: LegendKeyData = { - min: null, - max: null, - avg: null, - total: null, - hidden: false - }; - this.legendData.data.push(additionalLegendKeyData); - } - }); - }); - - this.datasources = this.datasources.concat(additionalDatasources); - - if (this.displayLegend) { - this.legendData.keys = this.legendData.keys.sort((key1, key2) => key1.dataKey.label.localeCompare(key2.dataKey.label)); - } - } */ - private resetData() { this.data.length = 0; this.hiddenData.length = 0; @@ -586,21 +463,6 @@ export class WidgetSubscription implements IWidgetSubscription { this.onDataUpdated(); } -/* private resetDataOld() { - for (let i = 0; i < this.data.length; i++) { - this.data[i].data = []; - this.hiddenData[i].data = []; - if (this.displayLegend) { - this.legendData.data[i].min = null; - this.legendData.data[i].max = null; - this.legendData.data[i].avg = null; - this.legendData.data[i].total = null; - this.legendData.data[i].hidden = false; - } - } - this.onDataUpdated(); - }*/ - getFirstEntityInfo(): SubscriptionEntityInfo { let entityId: EntityId; let entityName: string; @@ -969,83 +831,6 @@ export class WidgetSubscription implements IWidgetSubscription { } } - /* private doSubscribeOld() { - if (this.type === widgetType.rpc) { - return; - } - if (this.type === widgetType.alarm) { - this.alarmsSubscribe(); - } else { - this.notifyDataLoading(); - if (this.type === widgetType.timeseries && this.timeWindowConfig) { - this.updateRealtimeSubscription(); - if (this.comparisonEnabled) { - this.updateSubscriptionForComparison(); - } - if (this.subscriptionTimewindow.fixedWindow) { - this.onDataUpdated(); - } - } - let index = 0; - let forceUpdate = !this.datasources.length; - this.datasources.forEach((datasource) => { - const listener: DatasourceListener = { - subscriptionType: this.type, - subscriptionTimewindow: this.subscriptionTimewindow, - datasource, - entityType: datasource.entityType, - entityId: datasource.entityId, - dataUpdated: this.dataUpdated.bind(this), - updateRealtimeSubscription: () => { - this.subscriptionTimewindow = this.updateRealtimeSubscription(); - return this.subscriptionTimewindow; - }, - setRealtimeSubscription: (subscriptionTimewindow) => { - this.updateRealtimeSubscription(deepClone(subscriptionTimewindow)); - }, - datasourceIndex: index - }; - - if (this.comparisonEnabled && datasource.isAdditional) { - listener.subscriptionTimewindow = this.timewindowForComparison; - listener.updateRealtimeSubscription = () => { - this.subscriptionTimewindow = this.updateSubscriptionForComparison(); - return this.subscriptionTimewindow; - }; - listener.setRealtimeSubscription = () => { - this.updateSubscriptionForComparison(); - }; - } - - let entityFieldKey = false; - - for (let a = 0; a < datasource.dataKeys.length; a++) { - if (datasource.dataKeys[a].type !== DataKeyType.entityField) { - this.data[index + a].data = []; - } else { - entityFieldKey = true; - } - } - index += datasource.dataKeys.length; - this.datasourceListeners.push(listener); - - if (datasource.dataKeys.length) { - this.ctx.datasourceService.subscribeToDatasource(listener); - } - if (datasource.unresolvedStateEntity || entityFieldKey || - !datasource.dataKeys.length || - (datasource.type === DatasourceType.entity && !datasource.entityId) - ) { - forceUpdate = true; - } - }); - if (forceUpdate) { - this.notifyDataLoaded(); - this.onDataUpdated(); - } - } - } */ - private alarmsSubscribe() { this.notifyDataLoading(); if (this.timeWindowConfig) { @@ -1097,20 +882,6 @@ export class WidgetSubscription implements IWidgetSubscription { this.subscribed = false; } -/* unsubscribeOld() { - if (this.type !== widgetType.rpc) { - if (this.type === widgetType.alarm) { - this.alarmsUnsubscribe(); - } else { - this.datasourceListeners.forEach((listener) => { - this.ctx.datasourceService.unsubscribeFromDatasource(listener); - }); - this.datasourceListeners.length = 0; - this.resetData(); - } - } - } */ - private alarmsUnsubscribe() { if (this.alarmSourceListener) { this.ctx.alarmService.unsubscribeFromAlarms(this.alarmSourceListener); @@ -1448,48 +1219,6 @@ export class WidgetSubscription implements IWidgetSubscription { } } -/* private dataUpdatedOld(sourceData: DataSetHolder, datasourceIndex: number, dataKeyIndex: number, detectChanges: boolean) { - for (let x = 0; x < this.datasourceListeners.length; x++) { - this.datasources[x].dataReceived = this.datasources[x].dataReceived === true; - if (this.datasourceListeners[x].datasourceIndex === datasourceIndex && sourceData.data.length > 0) { - this.datasources[x].dataReceived = true; - } - } - this.notifyDataLoaded(); - let update = true; - let currentData: DataSetHolder; - if (this.displayLegend && this.legendData.keys[datasourceIndex + dataKeyIndex].dataKey.hidden) { - currentData = this.hiddenData[datasourceIndex + dataKeyIndex]; - } else { - currentData = this.data[datasourceIndex + dataKeyIndex]; - } - if (this.type === widgetType.latest) { - const prevData = currentData.data; - if (!sourceData.data.length) { - update = false; - } else if (prevData && prevData[0] && prevData[0].length > 1 && sourceData.data.length > 0) { - const prevTs = prevData[0][0]; - const prevValue = prevData[0][1]; - if (prevTs === sourceData.data[0][0] && prevValue === sourceData.data[0][1]) { - update = false; - } - } - } - if (update) { - if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { - this.updateTimewindow(); - if (this.timewindowForComparison && this.timewindowForComparison.realtimeWindowMs) { - this.updateComparisonTimewindow(); - } - } - currentData.data = sourceData.data; - if (this.caulculateLegendData) { - this.updateLegend(datasourceIndex + dataKeyIndex, sourceData.data, detectChanges); - } - this.onDataUpdated(detectChanges); - } - } */ - private alarmsUpdated(alarms: Array) { this.notifyDataLoaded(); const updated = !this.alarms || !isEqual(this.alarms, alarms); @@ -1522,7 +1251,6 @@ export class WidgetSubscription implements IWidgetSubscription { this.callbacks.legendDataUpdated(this, detectChanges !== false); } - private loadStDiff(): Observable { const loadSubject = new ReplaySubject(1); if (this.ctx.getServerTimeDiff && this.timeWindow) { diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 0cf66bacfd..cd6d8bf787 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -45,7 +45,6 @@ import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.m import { UtilsService } from '@core/services/utils.service'; import { AliasFilterType, EntityAlias, EntityAliasFilter, EntityAliasFilterResult } from '@shared/models/alias.models'; import { entityFields, EntityInfo, ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models'; -import { EntityRelationInfo, EntitySearchDirection } from '@shared/models/relation.models'; import { EntityRelationService } from '@core/http/entity-relation.service'; import { deepClone, isDefined, isDefinedAndNotNull } from '@core/utils'; import { Asset } from '@shared/models/asset.models'; @@ -669,7 +668,6 @@ export class EntityService { entityParamName: result.entityParamName, resolveMultiple: filter.resolveMultiple }; - aliasInfo.resolvedEntities = result.entities; aliasInfo.currentEntity = null; if (!aliasInfo.resolveMultiple && aliasInfo.entityFilter) { return this.findSingleEntityInfoByEntityFilter(aliasInfo.entityFilter, @@ -687,7 +685,6 @@ export class EntityService { public resolveAliasFilter(filter: EntityAliasFilter, stateParams: StateParams): Observable { const result: EntityAliasFilterResult = { - entities: [], entityFilter: null, stateEntity: false }; @@ -704,40 +701,12 @@ export class EntityService { singleEntity: aliasEntityId }; return of(result); - /*return this.getEntity(aliasEntityId.entityType as EntityType, aliasEntityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe( - map((entity) => { - result.entities = this.entitiesToEntitiesInfo([entity]); - return result; - } - ));*/ case AliasFilterType.entityList: result.entityFilter = deepClone(filter); return of(result); - /*return this.getEntities(filter.entityType, filter.entityList, {ignoreLoading: true, ignoreErrors: true}).pipe( - map((entities) => { - if (entities && entities.length || !failOnEmpty) { - result.entities = this.entitiesToEntitiesInfo(entities); - return result; - } else { - throw new Error(); - } - } - ));*/ case AliasFilterType.entityName: result.entityFilter = deepClone(filter); return of(result); - /*return this.getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems, - '', {ignoreLoading: true, ignoreErrors: true}).pipe( - map((entities) => { - if (entities && entities.length || !failOnEmpty) { - result.entities = this.entitiesToEntitiesInfo(entities); - return result; - } else { - throw new Error(); - } - } - ) - );*/ case AliasFilterType.stateEntity: result.stateEntity = true; if (stateEntityId) { @@ -747,60 +716,15 @@ export class EntityService { }; } return of(result); - /*return this.getEntity(stateEntityId.entityType as EntityType, stateEntityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe( - map((entity) => { - result.entities = this.entitiesToEntitiesInfo([entity]); - return result; - } - )); - } else { - return of(result); - }*/ case AliasFilterType.assetType: result.entityFilter = deepClone(filter); return of(result); - /*return this.getEntitiesByNameFilter(EntityType.ASSET, filter.assetNameFilter, maxItems, - filter.assetType, {ignoreLoading: true, ignoreErrors: true}).pipe( - map((entities) => { - if (entities && entities.length || !failOnEmpty) { - result.entities = this.entitiesToEntitiesInfo(entities); - return result; - } else { - throw new Error(); - } - } - ) - );*/ case AliasFilterType.deviceType: result.entityFilter = deepClone(filter); return of(result); - /*return this.getEntitiesByNameFilter(EntityType.DEVICE, filter.deviceNameFilter, maxItems, - filter.deviceType, {ignoreLoading: true, ignoreErrors: true}).pipe( - map((entities) => { - if (entities && entities.length || !failOnEmpty) { - result.entities = this.entitiesToEntitiesInfo(entities); - return result; - } else { - throw new Error(); - } - } - ) - );*/ case AliasFilterType.entityViewType: result.entityFilter = deepClone(filter); return of(result); - /*return this.getEntitiesByNameFilter(EntityType.ENTITY_VIEW, filter.entityViewNameFilter, maxItems, - filter.entityViewType, {ignoreLoading: true, ignoreErrors: true}).pipe( - map((entities) => { - if (entities && entities.length || !failOnEmpty) { - result.entities = this.entitiesToEntitiesInfo(entities); - return result; - } else { - throw new Error(); - } - } - ) - );*/ case AliasFilterType.relationsQuery: result.stateEntity = filter.rootStateEntity; let rootEntityType; @@ -817,34 +741,6 @@ export class EntityService { result.entityFilter = deepClone(filter); result.entityFilter.rootEntity = relationQueryRootEntityId; return of(result); - /*const searchQuery: EntityRelationsQuery = { - parameters: { - rootId: relationQueryRootEntityId.id, - rootType: relationQueryRootEntityId.entityType as EntityType, - direction: filter.direction, - fetchLastLevelOnly: filter.fetchLastLevelOnly - }, - filters: filter.filters - }; - searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; - return this.entityRelationService.findInfoByQuery(searchQuery, {ignoreLoading: true, ignoreErrors: true}).pipe( - mergeMap((allRelations) => { - if (allRelations && allRelations.length || !failOnEmpty) { - if (isDefined(maxItems) && maxItems > 0 && allRelations) { - const limit = Math.min(allRelations.length, maxItems); - allRelations.length = limit; - } - return this.entityRelationInfosToEntitiesInfo(allRelations, filter.direction).pipe( - map((entities) => { - result.entities = entities; - return result; - }) - ); - } else { - return throwError(null); - } - }) - );*/ } else { return of(result); } @@ -864,44 +760,6 @@ export class EntityService { result.entityFilter = deepClone(filter); result.entityFilter.rootEntity = searchQueryRootEntityId; return of(result); - /* const searchQuery: EntitySearchQuery = { - parameters: { - rootId: searchQueryRootEntityId.id, - rootType: searchQueryRootEntityId.entityType as EntityType, - direction: filter.direction, - fetchLastLevelOnly: filter.fetchLastLevelOnly - }, - relationType: filter.relationType - }; - searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; - let findByQueryObservable: Observable>>; - if (filter.type === AliasFilterType.assetSearchQuery) { - const assetSearchQuery = searchQuery as AssetSearchQuery; - assetSearchQuery.assetTypes = filter.assetTypes; - findByQueryObservable = this.assetService.findByQuery(assetSearchQuery, {ignoreLoading: true, ignoreErrors: true}); - } else if (filter.type === AliasFilterType.deviceSearchQuery) { - const deviceSearchQuery = searchQuery as DeviceSearchQuery; - deviceSearchQuery.deviceTypes = filter.deviceTypes; - findByQueryObservable = this.deviceService.findByQuery(deviceSearchQuery, {ignoreLoading: true, ignoreErrors: true}); - } else if (filter.type === AliasFilterType.entityViewSearchQuery) { - const entityViewSearchQuery = searchQuery as EntityViewSearchQuery; - entityViewSearchQuery.entityViewTypes = filter.entityViewTypes; - findByQueryObservable = this.entityViewService.findByQuery(entityViewSearchQuery, {ignoreLoading: true, ignoreErrors: true}); - } - return findByQueryObservable.pipe( - map((entities) => { - if (entities && entities.length || !failOnEmpty) { - if (isDefined(maxItems) && maxItems > 0 && entities) { - const limit = Math.min(entities.length, maxItems); - entities.length = limit; - } - result.entities = this.entitiesToEntitiesInfo(entities); - return result; - } else { - throw Error(); - } - }) - );*/ } else { return of(result); } @@ -915,12 +773,6 @@ export class EntityService { return true; } else { return isDefinedAndNotNull(result.entityFilter); - /*const entities = result.entities; - if (entities && entities.length) { - return true; - } else { - return false; - }*/ } }), catchError(err => of(false)) @@ -1074,73 +926,6 @@ export class EntityService { ); } - private entitiesToEntitiesInfo(entities: Array>): Array { - const entitiesInfo = []; - if (entities) { - entities.forEach((entity) => { - entitiesInfo.push(this.entityToEntityInfo(entity)); - }); - } - return entitiesInfo; - } - - private entityToEntityInfo(entity: BaseData): EntityInfo { - return { - origEntity: entity, - name: entity.name, - label: (entity as any).label ? (entity as any).label : '', - entityType: entity.id.entityType as EntityType, - id: entity.id.id, - entityDescription: (entity as any).additionalInfo ? (entity as any).additionalInfo.description : '' - }; - } - - private entityRelationInfosToEntitiesInfo(entityRelations: Array, - direction: EntitySearchDirection): Observable> { - if (entityRelations.length) { - const packs: Observable[][] = []; - let packTasks: Observable[] = []; - entityRelations.forEach((entityRelation) => { - packTasks.push(this.entityRelationInfoToEntityInfo(entityRelation, direction)); - if (packTasks.length === 100) { - packs.push(packTasks); - packTasks = []; - } - }); - if (packTasks.length) { - packs.push(packTasks); - } - return this.executePack(packs, 0); - } else { - return of([]); - } - } - - private executePack(packs: Observable[][], index: number): Observable> { - return forkJoin(packs[index]).pipe( - expand(() => { - index++; - if (packs[index]) { - return forkJoin(packs[index]); - } else { - return EMPTY; - } - } - ), - concatMap((data) => data), - toArray() - ); - } - - private entityRelationInfoToEntityInfo(entityRelationInfo: EntityRelationInfo, direction: EntitySearchDirection): Observable { - const entityId = direction === EntitySearchDirection.FROM ? entityRelationInfo.to : entityRelationInfo.from; - return this.getEntity(entityId.entityType as EntityType, entityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe( - map((entity) => { - return this.entityToEntityInfo(entity); - }) - ); - } - private getStateEntityInfo(filter: EntityAliasFilter, stateParams: StateParams): {entityId: EntityId} { let entityId: EntityId = null; if (stateParams) { diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.html b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.html index c94d65ea00..a0beb194b5 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.html @@ -24,14 +24,5 @@ [(ngModel)]="alias.value.currentEntity" (ngModelChange)="currentAliasEntityChanged(alias.key, alias.value.currentEntity)"> - diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.ts b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.ts index 31a7e32a1c..c05d4b844d 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.ts @@ -16,7 +16,6 @@ import { Component, Inject, InjectionToken } from '@angular/core'; import { AliasInfo, IAliasController } from '@core/api/widget-api.models'; -import { deepClone } from '@core/utils'; import { EntityInfo } from '@shared/models/entity.models'; export const ALIASES_ENTITY_SELECT_PANEL_DATA = new InjectionToken('AliasesEntitySelectPanelData'); @@ -40,12 +39,8 @@ export class AliasesEntitySelectPanelComponent { } public currentAliasEntityChanged(aliasId: string, selected: EntityInfo | null) { - // const resolvedEntities = this.entityAliasesInfo[aliasId].resolvedEntities; - // const selected = resolvedEntities.find((entity) => entity.id === selectedId); if (selected) { this.data.aliasController.updateCurrentAliasEntity(aliasId, selected); } } - - } diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts index d5ad291080..2852450d7d 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts @@ -178,9 +178,8 @@ export class AliasesEntitySelectComponent implements OnInit, OnDestroy { for (const aliasId of Object.keys(allEntityAliases)) { const aliasInfo = this.aliasController.getInstantAliasInfo(aliasId); if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity - && aliasInfo.entityFilter /*aliasInfo.resolvedEntities.length > 1*/) { + && aliasInfo.entityFilter) { this.entityAliasesInfo[aliasId] = deepClone(aliasInfo); - this.entityAliasesInfo[aliasId].selectedId = aliasInfo.currentEntity.id; this.hasSelectableAliasEntities = true; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index 95788348ca..3010e22bd6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -42,7 +42,7 @@ import { createLabelFromDatasource, deepClone, hashCode, isDefined, isNumber } f import cssjs from '@core/css/css'; import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; +import { BehaviorSubject, fromEvent, merge, Observable } from 'rxjs'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; import { EntityId } from '@shared/models/id/entity-id'; import { entityTypeTranslations } from '@shared/models/entity-type.models'; @@ -56,7 +56,11 @@ import { constructTableCssString, DisplayColumn, EntityColumn, - EntityData, entityDataSortOrderFromString, findColumnByEntityKey, findEntityKeyByColumnDef, + EntityData, + entityDataSortOrderFromString, + findColumnByEntityKey, + findEntityKeyByColumnDef, + fromEntityColumnDef, getCellContentInfo, getCellStyleInfo, getColumnWidth, @@ -79,6 +83,7 @@ import { EntityKeyType, KeyFilter } from '@shared/models/query/query.models'; +import { sortItems } from '@shared/models/page/page-link'; interface EntitiesTableWidgetSettings extends TableWidgetSettings { entitiesTitle: string; @@ -382,7 +387,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni if ($event) { $event.stopPropagation(); } - const target = $event.target || $event.srcElement || $event.currentTarget; + const target = $event.target || $event.currentTarget; const config = new OverlayConfig(); config.backdropClass = 'cdk-overlay-transparent-backdrop'; config.hasBackdrop = true; @@ -457,8 +462,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni key: findEntityKeyByColumnDef(this.sort.active, this.columns), direction: Direction[this.sort.direction.toUpperCase()] }; + const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns); const keyFilters: KeyFilter[] = null; // TODO: - this.entityDatasource.loadEntities(this.pageLink, keyFilters); + this.entityDatasource.loadEntities(this.pageLink, sortOrderLabel, keyFilters); this.ctx.detectChanges(); } @@ -483,7 +489,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni style = {}; } } else { - style = this.defaultStyle(key, value); + style = {}; } } if (!style.width) { @@ -497,7 +503,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni if (entity && key) { const contentInfo = this.contentsInfo[key.def]; const value = getEntityValue(entity, key); - let content = ''; + let content: string; if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { try { content = contentInfo.cellContentFunction(value, entity, this.ctx); @@ -549,15 +555,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, null, entityLabel); } - - private defaultStyle(key: EntityColumn, value: any): any { - return {}; - } - } - - class EntityDatasource implements DataSource { private entitiesSubject = new BehaviorSubject([]); @@ -567,6 +566,9 @@ class EntityDatasource implements DataSource { public dataLoading = true; + private appliedPageLink: EntityDataPageLink; + private appliedSortOrderLabel: string; + constructor( private translate: TranslateService, private dataKeys: Array, @@ -583,18 +585,24 @@ class EntityDatasource implements DataSource { this.pageDataSubject.complete(); } - loadEntities(pageLink: EntityDataPageLink, keyFilters: KeyFilter[]) { + loadEntities(pageLink: EntityDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) { this.dataLoading = true; + this.appliedPageLink = pageLink; + this.appliedSortOrderLabel = sortOrderLabel; this.subscription.subscribeForPaginatedData(0, pageLink, keyFilters); } dataUpdated() { const datasourcesPageData = this.subscription.datasourcePages[0]; const dataPageData = this.subscription.dataPages[0]; - const entities = new Array(); + let entities = new Array(); datasourcesPageData.data.forEach((datasource, index) => { entities.push(this.datasourceToEntityData(datasource, dataPageData.data[index])); }); + if (this.appliedSortOrderLabel && this.appliedSortOrderLabel.length) { + const asc = this.appliedPageLink.sortOrder.direction === Direction.ASC; + entities = entities.sort((a, b) => sortItems(a, b, this.appliedSortOrderLabel, asc)); + } const entitiesPageData: PageData = { data: entities, totalPages: datasourcesPageData.totalPages, @@ -624,8 +632,7 @@ class EntityDatasource implements DataSource { this.dataKeys.forEach((dataKey, index) => { const keyData = data[index].data; if (keyData && keyData.length && keyData[0].length > 1) { - const value = keyData[0][1]; - entity[dataKey.label] = value; + entity[dataKey.label] = keyData[0][1]; } else { entity[dataKey.label] = ''; } 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 9ed77d13a6..69275fa39c 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 @@ -165,7 +165,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI private entityService: EntityService, private alarmService: AlarmService, private dashboardService: DashboardService, - // private datasourceService: DatasourceService, private entityDataService: EntityDataService, private translate: TranslateService, private utils: UtilsService, @@ -299,7 +298,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.subscriptionContext.deviceService = this.deviceService; this.subscriptionContext.alarmService = this.alarmService; this.subscriptionContext.translate = this.translate; - // this.subscriptionContext.datasourceService = this.datasourceService; this.subscriptionContext.entityDataService = this.entityDataService; this.subscriptionContext.utils = this.utils; this.subscriptionContext.raf = this.raf; diff --git a/ui-ngx/src/app/shared/models/alias.models.ts b/ui-ngx/src/app/shared/models/alias.models.ts index 0ccde879a0..35b52435a2 100644 --- a/ui-ngx/src/app/shared/models/alias.models.ts +++ b/ui-ngx/src/app/shared/models/alias.models.ts @@ -156,7 +156,6 @@ export interface EntityAliases { } export interface EntityAliasFilterResult { - entities: Array; stateEntity: boolean; entityFilter: EntityFilter; entityParamName?: string; diff --git a/ui-ngx/src/app/shared/models/entity.models.ts b/ui-ngx/src/app/shared/models/entity.models.ts index 502f470f20..dcadb914d1 100644 --- a/ui-ngx/src/app/shared/models/entity.models.ts +++ b/ui-ngx/src/app/shared/models/entity.models.ts @@ -14,13 +14,10 @@ /// limitations under the License. /// -import { BaseData } from '@shared/models/base-data'; import { EntityType } from '@shared/models/entity-type.models'; -import { EntityId } from '@shared/models/id/entity-id'; import { AttributeData } from './telemetry/telemetry.models'; export interface EntityInfo { - origEntity?: BaseData; name?: string; label?: string; entityType?: EntityType; diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts index caf353e69a..712757a7f3 100644 --- a/ui-ngx/src/app/shared/models/page/page-link.ts +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -65,6 +65,28 @@ const defaultPageLinkSearch: PageLinkSearchFunction = return false; }; +export function sortItems(item1: any, item2: any, property: string, asc: boolean): number { + const item1Value = getDescendantProp(item1, property); + const item2Value = getDescendantProp(item2, property); + let result = 0; + if (item1Value !== item2Value) { + if (typeof item1Value === 'number' && typeof item2Value === 'number') { + result = item1Value - item2Value; + } else if (typeof item1Value === 'string' && typeof item2Value === 'string') { + result = item1Value.localeCompare(item2Value); + } else if (typeof item1Value === 'boolean' && typeof item2Value === 'boolean') { + if (item1Value && !item2Value) { + result = 1; + } else if (!item1Value && item2Value) { + result = -1; + } + } else if (typeof item1Value !== typeof item2Value) { + result = 1; + } + } + return asc ? result : result * -1; +} + export class PageLink { textSearch: string; @@ -96,26 +118,9 @@ export class PageLink { public sort(item1: any, item2: any): number { if (this.sortOrder) { - const property = this.sortOrder.property; - const item1Value = getDescendantProp(item1, property); - const item2Value = getDescendantProp(item2, property); - let result = 0; - if (item1Value !== item2Value) { - if (typeof item1Value === 'number' && typeof item2Value === 'number') { - result = item1Value - item2Value; - } else if (typeof item1Value === 'string' && typeof item2Value === 'string') { - result = item1Value.localeCompare(item2Value); - } else if (typeof item1Value === 'boolean' && typeof item2Value === 'boolean') { - if (item1Value && !item2Value) { - result = 1; - } else if (!item1Value && item2Value) { - result = -1; - } - } else if (typeof item1Value !== typeof item2Value) { - result = 1; - } - } - return this.sortOrder.direction === Direction.ASC ? result : result * -1; + const sortProperty = this.sortOrder.property; + const asc = this.sortOrder.direction === Direction.ASC; + return sortItems(item1, item2, sortProperty, asc); } return 0; } @@ -130,7 +135,9 @@ export class PageLink { pageData.totalElements = pageData.data.length; pageData.totalPages = this.pageSize === Number.POSITIVE_INFINITY ? 1 : Math.ceil(pageData.totalElements / this.pageSize); if (this.sortOrder) { - pageData.data = pageData.data.sort((a, b) => this.sort(a, b)); + const sortProperty = this.sortOrder.property; + const asc = this.sortOrder.direction === Direction.ASC; + pageData.data = pageData.data.sort((a, b) => sortItems(a, b, sortProperty, asc)); } if (this.pageSize !== Number.POSITIVE_INFINITY) { const startIndex = this.pageSize * this.page; diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index 27a376787c..44d5bb80ec 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -21,6 +21,8 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { EntityInfo } from '@shared/models/entity.models'; import { EntityType } from '@shared/models/entity-type.models'; import { Datasource, DatasourceType } from '@shared/models/widget.models'; +import { PageData } from '@shared/models/page/page-data'; +import { isEqual } from '@core/utils'; export enum EntityKeyType { ATTRIBUTE = 'ATTRIBUTE', @@ -189,6 +191,12 @@ export interface EntityData { timeseries: {[key: string]: Array}; } +export function entityPageDataChanged(prevPageData: PageData, nextPageData: PageData): boolean { + const prevIds = prevPageData.data.map((entityData) => entityData.entityId.id); + const nextIds = nextPageData.data.map((entityData) => entityData.entityId.id); + return !isEqual(prevIds, nextIds); +} + export const entityInfoFields: EntityKey[] = [ { type: EntityKeyType.ENTITY_FIELD,