Browse Source

Merge branch 'feature/entity-data-query' of github.com:thingsboard/thingsboard into feature/entity-data-query

pull/3053/head
Andrii Shvaika 6 years ago
parent
commit
744cbc16c7
  1. 1
      ui-ngx/src/app/core/api/alias-controller.ts
  2. 666
      ui-ngx/src/app/core/api/datasource-subcription.ts
  3. 109
      ui-ngx/src/app/core/api/datasource.service.ts
  4. 33
      ui-ngx/src/app/core/api/entity-data-subscription.ts
  5. 11
      ui-ngx/src/app/core/api/entity-data.service.ts
  6. 4
      ui-ngx/src/app/core/api/public-api.ts
  7. 4
      ui-ngx/src/app/core/api/widget-api.models.ts
  8. 272
      ui-ngx/src/app/core/api/widget-subscription.ts
  9. 215
      ui-ngx/src/app/core/http/entity.service.ts
  10. 9
      ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.html
  11. 5
      ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.ts
  12. 3
      ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts
  13. 41
      ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts
  14. 2
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  15. 1
      ui-ngx/src/app/shared/models/alias.models.ts
  16. 3
      ui-ngx/src/app/shared/models/entity.models.ts
  17. 49
      ui-ngx/src/app/shared/models/page/page-link.ts
  18. 8
      ui-ngx/src/app/shared/models/query/query.models.ts

1
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';

666
ui-ngx/src/app/core/api/datasource-subcription.ts

@ -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<SubscriptionDataKey>;
type: widgetType;
entityType?: EntityType;
entityId?: string;
subscriptionTimewindow?: SubscriptionTimewindow;
}
export class DatasourceSubscription {
private listeners: Array<DatasourceListener> = [];
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<TelemetrySubscriber>();
private dataAggregator: DataAggregator;
private dataKeys: {[key: string]: Array<SubscriptionDataKey> | 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<SubscriptionDataKey>;
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<SubscriptionDataKey>;
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<SubscriptionDataKey>;
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<SubscriptionDataKey>;
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<string>, 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<SubscriptionDataKey>;
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<SubscriptionDataKey>;
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;
}
}
}

109
ui-ngx/src/app/core/api/datasource.service.ts

@ -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<SubscriptionDataKey> = [];
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;
}
}
}

33
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<SubscriptionDataKey>;
@ -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<EntityData>, nextPageData: PageData<EntityData>) {
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<EntityData>) {
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);
}

11
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 {

4
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';

4
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<EntityInfo>;
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;

272
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<DatasourceData>;
datasources: Array<Datasource>;
// datasourceListeners: Array<DatasourceListener>;
hiddenData: Array<DataSetHolder>;
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<any> {
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<AlarmInfo>) {
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<any> {
const loadSubject = new ReplaySubject(1);
if (this.ctx.getServerTimeDiff && this.timeWindow) {

215
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<EntityAliasFilterResult> {
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<Array<BaseData<EntityId>>>;
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<BaseData<EntityId>>): Array<EntityInfo> {
const entitiesInfo = [];
if (entities) {
entities.forEach((entity) => {
entitiesInfo.push(this.entityToEntityInfo(entity));
});
}
return entitiesInfo;
}
private entityToEntityInfo(entity: BaseData<EntityId>): 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<EntityRelationInfo>,
direction: EntitySearchDirection): Observable<Array<EntityInfo>> {
if (entityRelations.length) {
const packs: Observable<EntityInfo>[][] = [];
let packTasks: Observable<EntityInfo>[] = [];
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<EntityInfo>[][], index: number): Observable<Array<EntityInfo>> {
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<EntityInfo> {
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) {

9
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)">
</tb-aliases-entity-autocomplete>
<!--mat-form-field fxFlex>
<mat-label>{{alias.value.alias}}</mat-label>
<mat-select [(ngModel)]="alias.value.selectedId"
(ngModelChange)="currentAliasEntityChanged(alias.key, alias.value.selectedId)">
<mat-option *ngFor="let resolvedEntity of alias.value.resolvedEntities" [value]="resolvedEntity.id">
{{resolvedEntity.name}}
</mat-option>
</mat-select>
</mat-form-field-->
</div>
</div>

5
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<any>('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);
}
}
}

3
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;
}
}

41
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<EntityData> {
private entitiesSubject = new BehaviorSubject<EntityData[]>([]);
@ -567,6 +566,9 @@ class EntityDatasource implements DataSource<EntityData> {
public dataLoading = true;
private appliedPageLink: EntityDataPageLink;
private appliedSortOrderLabel: string;
constructor(
private translate: TranslateService,
private dataKeys: Array<DataKey>,
@ -583,18 +585,24 @@ class EntityDatasource implements DataSource<EntityData> {
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<EntityData>();
let entities = new Array<EntityData>();
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<EntityData> = {
data: entities,
totalPages: datasourcesPageData.totalPages,
@ -624,8 +632,7 @@ class EntityDatasource implements DataSource<EntityData> {
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] = '';
}

2
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;

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

@ -156,7 +156,6 @@ export interface EntityAliases {
}
export interface EntityAliasFilterResult {
entities: Array<EntityInfo>;
stateEntity: boolean;
entityFilter: EntityFilter;
entityParamName?: string;

3
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<EntityId>;
name?: string;
label?: string;
entityType?: EntityType;

49
ui-ngx/src/app/shared/models/page/page-link.ts

@ -65,6 +65,28 @@ const defaultPageLinkSearch: PageLinkSearchFunction<any> =
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;

8
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<TsValue>};
}
export function entityPageDataChanged(prevPageData: PageData<EntityData>, nextPageData: PageData<EntityData>): 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,

Loading…
Cancel
Save