Browse Source

UI: latest data aggregation improvements

pull/7288/head
Igor Kulikov 4 years ago
parent
commit
f28297b7ae
  1. 242
      ui-ngx/src/app/core/api/data-aggregator.ts
  2. 347
      ui-ngx/src/app/core/api/entity-data-subscription.ts
  3. 4
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  4. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  5. 20
      ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts
  6. 3
      ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts
  7. 1
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  8. 1
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
  9. 6
      ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
  10. 25
      ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss
  11. 1
      ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts
  12. 63
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.html
  13. 53
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts
  14. 14
      ui-ngx/src/app/shared/components/time/timewindow.component.ts
  15. 1
      ui-ngx/src/app/shared/models/query/query.models.ts
  16. 5
      ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
  17. 15
      ui-ngx/src/app/shared/models/time/time.models.ts
  18. 2
      ui-ngx/src/app/shared/models/widget.models.ts

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

@ -14,7 +14,11 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { AggKey, SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; import {
AggKey,
AggSubscriptionData,
SubscriptionData
} from '@app/shared/models/telemetry/telemetry.models';
import { import {
AggregationType, AggregationType,
calculateIntervalComparisonEndTime, calculateIntervalComparisonEndTime,
@ -28,7 +32,7 @@ import { UtilsService } from '@core/services/utils.service';
import { deepClone, isDefinedAndNotNull, isNumber, isNumeric } from '@core/utils'; import { deepClone, isDefinedAndNotNull, isNumber, isNumeric } from '@core/utils';
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
export declare type onAggregatedData = (aggType: AggregationType, data: SubscriptionData, detectChanges: boolean) => void; export declare type onAggregatedData = (data: AggSubscriptionData, detectChanges: boolean) => void;
interface AggData { interface AggData {
count: number; count: number;
@ -67,30 +71,23 @@ class AggDataMap {
} }
class AggregationMap { class AggregationMap {
aggMap: {[aggType: string]: {[key: string]: AggDataMap}} = {}; aggMap: {[aggKey: string]: AggDataMap} = {};
detectRangeChanged(): boolean { detectRangeChanged(): boolean {
let changed = false; let changed = false;
for (const aggType of Object.keys(this.aggMap)) { for (const aggKey of Object.keys(this.aggMap)) {
const aggKeyMap = this.aggMap[aggType]; const aggDataMap = this.aggMap[aggKey];
for (const key of Object.keys(aggKeyMap)) { if (aggDataMap.rangeChanged) {
const aggDataMap = aggKeyMap[key]; changed = true;
if (aggDataMap.rangeChanged) { aggDataMap.rangeChanged = false;
changed = true;
aggDataMap.rangeChanged = false;
}
} }
} }
return changed; return changed;
} }
clearRangeChangedFlags() { clearRangeChangedFlags() {
for (const aggType of Object.keys(this.aggMap)) { for (const aggKey of Object.keys(this.aggMap)) {
const aggKeyMap = this.aggMap[aggType]; this.aggMap[aggKey].rangeChanged = false;
for (const key of Object.keys(aggKeyMap)) {
const aggDataMap = aggKeyMap[key];
aggDataMap.rangeChanged = false;
}
} }
} }
} }
@ -145,7 +142,6 @@ export class DataAggregator {
constructor(private onDataCb: onAggregatedData, constructor(private onDataCb: onAggregatedData,
private tsKeys: AggKey[], private tsKeys: AggKey[],
private isLatestDataAgg: boolean, private isLatestDataAgg: boolean,
private isFloatingLatestDataAgg: boolean,
private subsTw: SubscriptionTimewindow, private subsTw: SubscriptionTimewindow,
private utils: UtilsService, private utils: UtilsService,
private ignoreDataUpdateOnIntervalTick: boolean) { private ignoreDataUpdateOnIntervalTick: boolean) {
@ -160,8 +156,8 @@ export class DataAggregator {
} }
} }
private dataBuffer: {[aggType: string]: SubscriptionData} = {}; private dataBuffer: AggSubscriptionData = {};
private data: {[aggType: string]: SubscriptionData}; private data: AggSubscriptionData;
private readonly lastPrevKvPairData: {[aggKey: string]: [number, any]}; private readonly lastPrevKvPairData: {[aggKey: string]: [number, any]};
private aggregationMap: AggregationMap; private aggregationMap: AggregationMap;
@ -209,6 +205,13 @@ export class DataAggregator {
return `${aggKey.key}_${aggKey.agg}`; return `${aggKey.key}_${aggKey.agg}`;
} }
private static aggKeyFromString(aggKeyString: string): AggKey {
const separatorIndex = aggKeyString.lastIndexOf('_');
const key = aggKeyString.substring(0, separatorIndex);
const agg = AggregationType[aggKeyString.substring(separatorIndex + 1)];
return { key, agg };
}
public updateOnDataCb(newOnDataCb: onAggregatedData): onAggregatedData { public updateOnDataCb(newOnDataCb: onAggregatedData): onAggregatedData {
const prevOnDataCb = this.onDataCb; const prevOnDataCb = this.onDataCb;
this.onDataCb = newOnDataCb; this.onDataCb = newOnDataCb;
@ -238,8 +241,7 @@ export class DataAggregator {
this.aggregationMap = null; this.aggregationMap = null;
} }
public onData(aggType: AggregationType, public onData(data: AggSubscriptionData, update: boolean, history: boolean, detectChanges: boolean) {
data: SubscriptionDataHolder, update: boolean, history: boolean, detectChanges: boolean) {
this.updatedData = true; this.updatedData = true;
if (!this.dataReceived || this.resetPending) { if (!this.dataReceived || this.resetPending) {
let updateIntervalScheduledTime = true; let updateIntervalScheduledTime = true;
@ -252,11 +254,11 @@ export class DataAggregator {
this.resetPending = false; this.resetPending = false;
updateIntervalScheduledTime = false; updateIntervalScheduledTime = false;
} }
this.aggregationMap = new AggregationMap();
if (update) { if (update) {
this.updateAggregatedData(aggType, data.data); this.aggregationMap = new AggregationMap();
this.updateAggregatedData(data);
} else { } else {
this.processAggregatedData(aggType, data.data); this.aggregationMap = this.processAggregatedData(data);
} }
if (updateIntervalScheduledTime) { if (updateIntervalScheduledTime) {
this.intervalScheduledTime = this.utils.currentPerfTime(); this.intervalScheduledTime = this.utils.currentPerfTime();
@ -264,11 +266,7 @@ export class DataAggregator {
this.aggregationMap.clearRangeChangedFlags(); this.aggregationMap.clearRangeChangedFlags();
this.onInterval(history, detectChanges); this.onInterval(history, detectChanges);
} else { } else {
if (this.aggregationMap.aggMap[aggType]) { this.updateAggregatedData(data);
this.updateAggregatedData(aggType, data.data);
} else {
this.processAggregatedData(aggType, data.data);
}
if (history) { if (history) {
this.intervalScheduledTime = this.utils.currentPerfTime(); this.intervalScheduledTime = this.utils.currentPerfTime();
this.onInterval(history, detectChanges); this.onInterval(history, detectChanges);
@ -324,9 +322,7 @@ export class DataAggregator {
this.data = this.updateData(); this.data = this.updateData();
} }
if (this.onDataCb && (!this.ignoreDataUpdateOnIntervalTick || this.updatedData)) { if (this.onDataCb && (!this.ignoreDataUpdateOnIntervalTick || this.updatedData)) {
for (const aggType of Object.keys(this.data)) { this.onDataCb(this.data, detectChanges);
this.onDataCb(AggregationType[aggType], this.data[aggType], detectChanges);
}
this.updatedData = false; this.updatedData = false;
} }
if (!history) { if (!history) {
@ -342,52 +338,32 @@ export class DataAggregator {
} }
this.dataBuffer[key.agg][key.key] = []; this.dataBuffer[key.agg][key.key] = [];
}); });
for (const aggType of Object.keys(this.aggregationMap.aggMap)) { for (const aggKeyString of Object.keys(this.aggregationMap.aggMap)) {
for (const key of Object.keys(this.aggregationMap.aggMap[aggType])) { const aggKeyData = this.aggregationMap.aggMap[aggKeyString];
const aggKeyData = this.aggregationMap.aggMap[aggType][key]; const aggKey = DataAggregator.aggKeyFromString(aggKeyString);
const noAggregation = AggregationType[aggType] === AggregationType.NONE; const noAggregation = aggKey.agg === AggregationType.NONE;
const aggKeyString = DataAggregator.aggKeyToString({agg: AggregationType[aggType], key}); let keyData = this.dataBuffer[aggKey.agg][aggKey.key];
let keyData = this.dataBuffer[aggType][key]; aggKeyData.forEach((aggData, aggTimestamp) => {
aggKeyData.forEach((aggData, aggTimestamp) => { if (aggTimestamp < this.startTs) {
if (aggTimestamp < this.startTs) { if (this.subsTw.aggregation.stateData &&
if (this.subsTw.aggregation.stateData && (!this.lastPrevKvPairData[aggKeyString] || this.lastPrevKvPairData[aggKeyString][0] < aggTimestamp)) {
(!this.lastPrevKvPairData[aggKeyString] || this.lastPrevKvPairData[aggKeyString][0] < aggTimestamp)) { this.lastPrevKvPairData[aggKeyString] = [aggTimestamp, aggData.aggValue];
this.lastPrevKvPairData[aggKeyString] = [aggTimestamp, aggData.aggValue];
}
aggKeyData.delete(aggTimestamp);
this.updatedData = true;
} else if (aggTimestamp < this.endTs || noAggregation) {
const kvPair: [number, any] = [aggTimestamp, aggData.aggValue];
keyData.push(kvPair);
}
});
keyData.sort((set1, set2) => set1[0] - set2[0]);
if (this.isFloatingLatestDataAgg) {
if (keyData.length) {
const timestamp = this.startTs + (this.endTs - this.startTs) / 2;
const first = keyData[0];
const aggData: AggData = {
aggValue: AggregationType[aggType] === AggregationType.COUNT ? 1 : first[1],
sum: first[1],
count: 1
};
const aggFunction = DataAggregator.getAggFunction(AggregationType[aggType]);
for (let i = 1; i < keyData.length; i++) {
const kvPair = keyData[i];
aggFunction(aggData, kvPair[1]);
}
this.dataBuffer[aggType][key] = [[timestamp, aggData.aggValue]];
} }
} else { aggKeyData.delete(aggTimestamp);
if (this.subsTw.aggregation.stateData) { this.updatedData = true;
this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[aggKeyString])); } else if (aggTimestamp < this.endTs || noAggregation) {
} const kvPair: [number, any] = [aggTimestamp, aggData.aggValue];
if (keyData.length > this.subsTw.aggregation.limit) { keyData.push(kvPair);
keyData = keyData.slice(keyData.length - this.subsTw.aggregation.limit);
}
this.dataBuffer[aggType][key] = keyData;
} }
});
keyData.sort((set1, set2) => set1[0] - set2[0]);
if (this.subsTw.aggregation.stateData) {
this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[aggKeyString]));
} }
if (keyData.length > this.subsTw.aggregation.limit) {
keyData = keyData.slice(keyData.length - this.subsTw.aggregation.limit);
}
this.dataBuffer[aggKey.agg][aggKey.key] = keyData;
} }
return this.dataBuffer; return this.dataBuffer;
} }
@ -420,68 +396,68 @@ export class DataAggregator {
} }
} }
private processAggregatedData(aggType: AggregationType, data: SubscriptionData) { private processAggregatedData(data: AggSubscriptionData): AggregationMap {
const isCount = aggType === AggregationType.COUNT; const aggregationMap = new AggregationMap();
const noAggregation = aggType === AggregationType.NONE || this.isFloatingLatestDataAgg; for (const aggTypeString of Object.keys(data)) {
let aggDataByKey = this.aggregationMap.aggMap[aggType]; const aggType = AggregationType[aggTypeString];
if (!aggDataByKey) { const isCount = aggType === AggregationType.COUNT;
aggDataByKey = {}; const noAggregation = aggType === AggregationType.NONE;
this.aggregationMap.aggMap[aggType] = aggDataByKey; for (const key of Object.keys(data[aggType])) {
} const aggKey = DataAggregator.aggKeyToString({key, agg: aggType});
for (const key of Object.keys(data)) { let aggKeyData = aggregationMap.aggMap[aggKey];
let aggKeyData = aggDataByKey[key]; if (!aggKeyData) {
if (!aggKeyData) { aggKeyData = new AggDataMap();
aggKeyData = new AggDataMap(); aggregationMap.aggMap[aggKey] = aggKeyData;
aggDataByKey[key] = aggKeyData; }
const keyData = data[aggType][key];
keyData.forEach((kvPair) => {
const timestamp = kvPair[0];
const value = DataAggregator.convertValue(kvPair[1], noAggregation);
const tsKey = timestamp;
const aggData = {
count: isCount ? value : isDefinedAndNotNull(kvPair[2]) ? kvPair[2] : 1,
sum: value,
aggValue: value
};
aggKeyData.set(tsKey, aggData);
});
} }
const keyData = data[key];
keyData.forEach((kvPair) => {
const timestamp = kvPair[0];
const value = DataAggregator.convertValue(kvPair[1], noAggregation);
const tsKey = timestamp;
const aggData = {
count: isCount ? value : isDefinedAndNotNull(kvPair[2]) ? kvPair[2] : 1,
sum: value,
aggValue: value
};
aggKeyData.set(tsKey, aggData);
});
} }
return aggregationMap;
} }
private updateAggregatedData(aggType: AggregationType, data: SubscriptionData) { private updateAggregatedData(data: AggSubscriptionData) {
const isCount = aggType === AggregationType.COUNT; for (const aggTypeString of Object.keys(data)) {
const noAggregation = aggType === AggregationType.NONE || this.isFloatingLatestDataAgg; const aggType = AggregationType[aggTypeString];
let aggDataByKey = this.aggregationMap.aggMap[aggType]; const isCount = aggType === AggregationType.COUNT;
if (!aggDataByKey) { const noAggregation = aggType === AggregationType.NONE;
aggDataByKey = {}; for (const key of Object.keys(data[aggType])) {
this.aggregationMap.aggMap[aggType] = aggDataByKey; const aggKey = DataAggregator.aggKeyToString({key, agg: aggType});
} let aggKeyData = this.aggregationMap.aggMap[aggKey];
for (const key of Object.keys(data)) { if (!aggKeyData) {
let aggKeyData = aggDataByKey[key]; aggKeyData = new AggDataMap();
if (!aggKeyData) { this.aggregationMap.aggMap[aggKey] = aggKeyData;
aggKeyData = new AggDataMap();
aggDataByKey[key] = aggKeyData;
}
const keyData = data[key];
keyData.forEach((kvPair) => {
const timestamp = kvPair[0];
const value = DataAggregator.convertValue(kvPair[1], noAggregation);
const aggTimestamp = noAggregation ? timestamp : (this.startTs +
Math.floor((timestamp - this.startTs) / this.subsTw.aggregation.interval) *
this.subsTw.aggregation.interval + this.subsTw.aggregation.interval / 2);
let aggData = aggKeyData.get(aggTimestamp);
if (!aggData || this.isFloatingLatestDataAgg) {
aggData = {
count: isDefinedAndNotNull(kvPair[2]) ? kvPair[2] : 1,
sum: value,
aggValue: isCount ? 1 : value
};
aggKeyData.set(aggTimestamp, aggData);
} else {
DataAggregator.getAggFunction(aggType)(aggData, value);
} }
}); const keyData = data[aggType][key];
keyData.forEach((kvPair) => {
const timestamp = kvPair[0];
const value = DataAggregator.convertValue(kvPair[1], noAggregation);
const aggTimestamp = noAggregation ? timestamp : (this.startTs +
Math.floor((timestamp - this.startTs) / this.subsTw.aggregation.interval) *
this.subsTw.aggregation.interval + this.subsTw.aggregation.interval / 2);
let aggData = aggKeyData.get(aggTimestamp);
if (!aggData) {
aggData = {
count: isDefinedAndNotNull(kvPair[2]) ? kvPair[2] : 1,
sum: value,
aggValue: isCount ? 1 : value
};
aggKeyData.set(aggTimestamp, aggData);
} else {
DataAggregator.getAggFunction(aggType)(aggData, value);
}
});
}
} }
} }

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

@ -29,11 +29,11 @@ import {
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
import { import {
AggKey, AggKey,
AggSubscriptionData,
DataKeyType, DataKeyType,
EntityCountCmd, EntityCountCmd,
EntityDataCmd, EntityDataCmd,
SubscriptionData, SubscriptionData,
SubscriptionDataHolder,
TelemetryService, TelemetryService,
TelemetrySubscriber TelemetrySubscriber
} from '@shared/models/telemetry/telemetry.models'; } from '@shared/models/telemetry/telemetry.models';
@ -46,8 +46,8 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { Observable, of, ReplaySubject, Subject } from 'rxjs'; import { Observable, of, ReplaySubject, Subject } from 'rxjs';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import Timeout = NodeJS.Timeout;
import _ from 'lodash'; import _ from 'lodash';
import Timeout = NodeJS.Timeout;
declare type DataKeyFunction = (time: number, prevValue: any) => any; declare type DataKeyFunction = (time: number, prevValue: any) => any;
declare type DataKeyPostFunction = (time: number, value: any, prevValue: any, timePrev: number, prevOrigValue: any) => any; declare type DataKeyPostFunction = (time: number, value: any, prevValue: any, timePrev: number, prevOrigValue: any) => any;
@ -94,6 +94,7 @@ export class EntityDataSubscription {
private entityDataSubscriptionOptions = this.listener.subscriptionOptions; private entityDataSubscriptionOptions = this.listener.subscriptionOptions;
private datasourceType: DatasourceType = this.entityDataSubscriptionOptions.datasourceType; private datasourceType: DatasourceType = this.entityDataSubscriptionOptions.datasourceType;
private history: boolean; private history: boolean;
private isFloatingTimewindow: boolean;
private realtime: boolean; private realtime: boolean;
private subscriber: TelemetrySubscriber; private subscriber: TelemetrySubscriber;
@ -108,6 +109,7 @@ export class EntityDataSubscription {
private entityDataResolveSubject: Subject<EntityDataLoadResult>; private entityDataResolveSubject: Subject<EntityDataLoadResult>;
private pageData: PageData<EntityData>; private pageData: PageData<EntityData>;
private data: Array<Array<DataSetHolder>>;
private subsTw: SubscriptionTimewindow; private subsTw: SubscriptionTimewindow;
private latestTsOffset: number; private latestTsOffset: number;
private dataAggregators: Array<DataAggregator>; private dataAggregators: Array<DataAggregator>;
@ -304,19 +306,28 @@ export class EntityDataSubscription {
this.subscriber.reconnect$.subscribe(() => { this.subscriber.reconnect$.subscribe(() => {
if (this.started) { if (this.started) {
const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand; const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand;
if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && if (!this.history && (this.entityDataSubscriptionOptions.type === widgetType.timeseries && this.tsFields.length ||
!this.history && this.tsFields.length) { this.aggTsValues.length > 0 && !this.isFloatingTimewindow)) {
const newSubsTw = this.listener.updateRealtimeSubscription(); const newSubsTw = this.listener.updateRealtimeSubscription();
this.subsTw = newSubsTw; this.subsTw = newSubsTw;
targetCommand.tsCmd.startTs = this.subsTw.startTs; if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && this.tsFields.length) {
targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow; targetCommand.tsCmd.startTs = this.subsTw.startTs;
targetCommand.tsCmd.interval = this.subsTw.aggregation.interval; targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow;
targetCommand.tsCmd.limit = this.subsTw.aggregation.limit; targetCommand.tsCmd.interval = this.subsTw.aggregation.interval;
targetCommand.tsCmd.agg = this.subsTw.aggregation.type; targetCommand.tsCmd.limit = this.subsTw.aggregation.limit;
targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData; targetCommand.tsCmd.agg = this.subsTw.aggregation.type;
this.dataAggregators.forEach((dataAggregator) => { targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData;
dataAggregator.reset(newSubsTw); this.dataAggregators.forEach((dataAggregator) => {
}); dataAggregator.reset(newSubsTw);
});
}
if (this.aggTsValues.length > 0 && !this.isFloatingTimewindow) {
targetCommand.aggTsCmd.startTs = this.subsTw.startTs;
targetCommand.aggTsCmd.timeWindow = this.subsTw.aggregation.timeWindow;
this.tsLatestDataAggregators.forEach((dataAggregator) => {
dataAggregator.reset(newSubsTw);
});
}
} }
if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
this.subscriber.setTsOffset(this.subsTw.tsOffset); this.subscriber.setTsOffset(this.subsTw.tsOffset);
@ -485,9 +496,12 @@ export class EntityDataSubscription {
isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow);
this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow &&
isDefinedAndNotNull(this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs); isDefinedAndNotNull(this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs);
this.isFloatingTimewindow = this.entityDataSubscriptionOptions.subscriptionTimewindow &&
!this.entityDataSubscriptionOptions.subscriptionTimewindow.quickInterval && !this.history;
} }
private prepareSubscriptionCommands(cmd: EntityDataCmd) { private prepareSubscriptionCommands(cmd: EntityDataCmd) {
let latestValuesKeys: EntityKey[] = [];
if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
if (this.tsFields.length > 0) { if (this.tsFields.length > 0) {
if (this.history) { if (this.history) {
@ -512,17 +526,9 @@ export class EntityDataSubscription {
}; };
} }
} }
if (this.latestValues.length > 0) { latestValuesKeys = this.latestValues;
cmd.latestCmd = {
keys: this.latestValues
};
}
} else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) {
if (this.latestValues.length > 0) { latestValuesKeys = this.latestValues;
cmd.latestCmd = {
keys: this.latestValues
};
}
} }
if (this.aggTsValues.length > 0) { if (this.aggTsValues.length > 0) {
if (this.history) { if (this.history) {
@ -531,15 +537,24 @@ export class EntityDataSubscription {
startTs: this.subsTw.fixedWindow.startTimeMs, startTs: this.subsTw.fixedWindow.startTimeMs,
endTs: this.subsTw.fixedWindow.endTimeMs endTs: this.subsTw.fixedWindow.endTimeMs
}; };
} else { } else if (!this.isFloatingTimewindow) {
cmd.aggTsCmd = { cmd.aggTsCmd = {
keys: this.aggTsValues, keys: this.aggTsValues,
startTs: this.subsTw.startTs, startTs: this.subsTw.startTs,
timeWindow: this.subsTw.aggregation.timeWindow, timeWindow: this.subsTw.aggregation.timeWindow
floating: !this.subsTw.quickInterval
}; };
if (latestValuesKeys.length > 0) {
const tsKeys = this.aggTsValues.map(key => key.key);
latestValuesKeys = latestValuesKeys.filter(latestKey => latestKey.type !== EntityKeyType.TIME_SERIES
|| !tsKeys.includes(latestKey.key));
}
} }
} }
if (latestValuesKeys.length > 0) {
cmd.latestCmd = {
keys: latestValuesKeys
};
}
} }
private startFunction() { private startFunction() {
@ -595,30 +610,50 @@ export class EntityDataSubscription {
for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) { for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) {
if (this.datasourceType === DatasourceType.function) { if (this.datasourceType === DatasourceType.function) {
this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, aggKeys, this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, aggKeys,
false, false, DataKeyType.function, dataIndex, this.notifyListener.bind(this)); false, DataKeyType.function, dataIndex, this.notifyListener.bind(this));
} else { } else {
this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, aggKeys, this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, aggKeys,
false, false, DataKeyType.timeseries, dataIndex, this.notifyListener.bind(this)); false, DataKeyType.timeseries, dataIndex, this.notifyListener.bind(this));
} }
} }
} }
} }
if (this.aggTsValues && this.aggTsValues.length) { if (this.aggTsValues && this.aggTsValues.length) {
const aggLatestTimewindow = deepClone(this.subsTw); if (!this.isFloatingTimewindow) {
aggLatestTimewindow.aggregation.stateData = false; const aggLatestTimewindow = deepClone(this.subsTw);
const isFloatingLatestDataAgg = !aggLatestTimewindow.quickInterval && !this.history; aggLatestTimewindow.aggregation.stateData = false;
if (!isFloatingLatestDataAgg) {
aggLatestTimewindow.aggregation.interval = aggLatestTimewindow.aggregation.timeWindow; aggLatestTimewindow.aggregation.interval = aggLatestTimewindow.aggregation.timeWindow;
} for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) {
for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) { this.tsLatestDataAggregators[dataIndex] = this.createRealtimeDataAggregator(aggLatestTimewindow, this.aggTsValues,
this.tsLatestDataAggregators[dataIndex] = this.createRealtimeDataAggregator(aggLatestTimewindow, this.aggTsValues, true, DataKeyType.timeseries, dataIndex, this.notifyListener.bind(this));
true, isFloatingLatestDataAgg, }
DataKeyType.timeseries, dataIndex, this.notifyListener.bind(this)); } else {
const tsKeysByAggType = _.groupBy(this.aggTsValues, value => value.agg);
const aggSubscriptionData: AggSubscriptionData = {};
for (const aggTypeString of Object.keys(tsKeysByAggType)) {
const tsKeys = tsKeysByAggType[aggTypeString];
const latestTsAggSubsciptionData: SubscriptionData = {};
for (const tsKey of tsKeys) {
latestTsAggSubsciptionData[tsKey.key] = [[0, 'Not supported!']];
}
aggSubscriptionData[aggTypeString] = latestTsAggSubsciptionData;
}
for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) {
this.onAggData(aggSubscriptionData, DataKeyType.timeseries, dataIndex, true,
this.entityDataSubscriptionOptions.type === widgetType.timeseries, true,
(data1, dataIndex1, dataKeyIndex) => {
if (!this.data[dataIndex1]) {
this.data[dataIndex1] = [];
}
this.data[dataIndex1][dataKeyIndex] = data1;
});
}
} }
} }
} }
private resetData() { private resetData() {
this.data = [];
this.datasourceData = []; this.datasourceData = [];
this.entityIdToDataIndex = {}; this.entityIdToDataIndex = {};
for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) { for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) {
@ -675,15 +710,14 @@ export class EntityDataSubscription {
} else if (isInitialData) { } else if (isInitialData) {
this.resetData(); this.resetData();
} }
const data: Array<Array<DataSetHolder>> = [];
for (let dataIndex = 0; dataIndex < pageData.data.length; dataIndex++) { for (let dataIndex = 0; dataIndex < pageData.data.length; dataIndex++) {
const entityData = pageData.data[dataIndex]; const entityData = pageData.data[dataIndex];
this.processEntityData(entityData, dataIndex, false, this.processEntityData(entityData, dataIndex, false,
(data1, dataIndex1, dataKeyIndex) => { (data1, dataIndex1, dataKeyIndex) => {
if (!data[dataIndex1]) { if (!this.data[dataIndex1]) {
data[dataIndex1] = []; this.data[dataIndex1] = [];
} }
data[dataIndex1][dataKeyIndex] = data1; this.data[dataIndex1][dataKeyIndex] = data1;
} }
); );
} }
@ -692,7 +726,7 @@ export class EntityDataSubscription {
this.entityDataResolveSubject.next( this.entityDataResolveSubject.next(
{ {
pageData, pageData,
data, data: this.data,
datasourceIndex: this.listener.configDatasourceIndex, datasourceIndex: this.listener.configDatasourceIndex,
pageLink: this.entityDataSubscriptionOptions.pageLink pageLink: this.entityDataSubscriptionOptions.pageLink
} }
@ -700,7 +734,7 @@ export class EntityDataSubscription {
this.entityDataResolveSubject.complete(); this.entityDataResolveSubject.complete();
} else { } else {
if (isInitialData || this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { if (isInitialData || this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
this.listener.dataLoaded(pageData, data, this.listener.dataLoaded(pageData, this.data,
this.listener.configDatasourceIndex, this.entityDataSubscriptionOptions.pageLink); this.listener.configDatasourceIndex, this.entityDataSubscriptionOptions.pageLink);
} }
if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription && isInitialData) { if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription && isInitialData) {
@ -710,7 +744,7 @@ export class EntityDataSubscription {
this.entityDataResolveSubject.next( this.entityDataResolveSubject.next(
{ {
pageData, pageData,
data, data: this.data,
datasourceIndex: this.listener.configDatasourceIndex, datasourceIndex: this.listener.configDatasourceIndex,
pageLink: this.entityDataSubscriptionOptions.pageLink pageLink: this.entityDataSubscriptionOptions.pageLink
} }
@ -740,46 +774,20 @@ export class EntityDataSubscription {
if (this.entityDataSubscriptionOptions.type === widgetType.latest || if (this.entityDataSubscriptionOptions.type === widgetType.latest ||
this.entityDataSubscriptionOptions.type === widgetType.timeseries) { this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
if (entityData.aggLatest) { if (entityData.aggLatest) {
for (const aggTypeString of Object.keys(entityData.aggLatest)) { if (this.tsLatestDataAggregators && this.tsLatestDataAggregators[dataIndex]) {
const subscriptionData = this.toSubscriptionData(entityData.aggLatest[aggTypeString], false);
if (this.tsLatestDataAggregators && this.tsLatestDataAggregators[dataIndex]) {
const dataAggregator = this.tsLatestDataAggregators[dataIndex];
let prevDataCb;
if (!isUpdate) {
prevDataCb = dataAggregator.updateOnDataCb((aggType, data, detectChanges) => {
this.onData(aggType, data, DataKeyType.timeseries, dataIndex, detectChanges,
this.entityDataSubscriptionOptions.type === widgetType.timeseries, dataUpdatedCb);
});
}
dataAggregator.onData(AggregationType[aggTypeString], {data: subscriptionData}, false, this.history, true);
if (prevDataCb) {
dataAggregator.updateOnDataCb(prevDataCb);
}
}
}
}
if (entityData.aggFloating) {
const subscriptionData = this.toSubscriptionData(entityData.aggFloating, true);
const keys: string[] = Object.keys(subscriptionData);
const aggTsKeys = this.aggTsValues.filter(key => keys.includes(key.key));
if (aggTsKeys.length && this.tsLatestDataAggregators && this.tsLatestDataAggregators[dataIndex]) {
const dataAggregator = this.tsLatestDataAggregators[dataIndex]; const dataAggregator = this.tsLatestDataAggregators[dataIndex];
const aggSubscriptionData: AggSubscriptionData = {};
for (const aggTypeString of Object.keys(entityData.aggLatest)) {
aggSubscriptionData[aggTypeString] = this.toSubscriptionData(entityData.aggLatest[aggTypeString], false);
}
let prevDataCb; let prevDataCb;
if (!isUpdate) { if (!isUpdate) {
prevDataCb = dataAggregator.updateOnDataCb((aggType, data, detectChanges) => { prevDataCb = dataAggregator.updateOnDataCb((data, detectChanges) => {
this.onData(aggType, data, DataKeyType.timeseries, dataIndex, detectChanges, this.onAggData(data, DataKeyType.timeseries, dataIndex, detectChanges,
this.entityDataSubscriptionOptions.type === widgetType.timeseries, dataUpdatedCb); this.entityDataSubscriptionOptions.type === widgetType.timeseries, true, dataUpdatedCb);
}); });
} }
const tsKeysByAggType = _.groupBy(aggTsKeys, value => value.agg); dataAggregator.onData(aggSubscriptionData, false, this.history, true);
for (const aggTypeString of Object.keys(tsKeysByAggType)) {
const tsKeys = tsKeysByAggType[aggTypeString];
const latestTsAggSubsciptionData: SubscriptionData = {};
for (const tsKey of tsKeys) {
latestTsAggSubsciptionData[tsKey.key] = subscriptionData[tsKey.key];
}
dataAggregator.onData(AggregationType[aggTypeString], {data: latestTsAggSubsciptionData}, false, this.history, true);
}
if (prevDataCb) { if (prevDataCb) {
dataAggregator.updateOnDataCb(prevDataCb); dataAggregator.updateOnDataCb(prevDataCb);
} }
@ -797,25 +805,27 @@ export class EntityDataSubscription {
for (const latestTsKey of latestTsKeys) { for (const latestTsKey of latestTsKeys) {
latestTsSubsciptionData[latestTsKey.key] = subscriptionData[latestTsKey.key]; latestTsSubsciptionData[latestTsKey.key] = subscriptionData[latestTsKey.key];
} }
this.onData(null, latestTsSubsciptionData, dataKeyType, dataIndex, true, this.onData(latestTsSubsciptionData, dataKeyType, dataIndex, true,
this.entityDataSubscriptionOptions.type === widgetType.timeseries, dataUpdatedCb); this.entityDataSubscriptionOptions.type === widgetType.timeseries, false, dataUpdatedCb);
} }
const aggTsKeys = this.aggTsValues.filter(key => keys.includes(key.key)); const aggTsKeys = this.aggTsValues.filter(key => keys.includes(key.key));
if (aggTsKeys.length && this.tsLatestDataAggregators && this.tsLatestDataAggregators[dataIndex]) { if (!this.history && aggTsKeys.length && this.tsLatestDataAggregators && this.tsLatestDataAggregators[dataIndex]) {
const dataAggregator = this.tsLatestDataAggregators[dataIndex]; const dataAggregator = this.tsLatestDataAggregators[dataIndex];
const tsKeysByAggType = _.groupBy(aggTsKeys, value => value.agg); const tsKeysByAggType = _.groupBy(aggTsKeys, value => value.agg);
const aggSubscriptionData: AggSubscriptionData = {};
for (const aggTypeString of Object.keys(tsKeysByAggType)) { for (const aggTypeString of Object.keys(tsKeysByAggType)) {
const tsKeys = tsKeysByAggType[aggTypeString]; const tsKeys = tsKeysByAggType[aggTypeString];
const latestTsAggSubsciptionData: SubscriptionData = {}; const latestTsAggSubsciptionData: SubscriptionData = {};
for (const tsKey of tsKeys) { for (const tsKey of tsKeys) {
latestTsAggSubsciptionData[tsKey.key] = subscriptionData[tsKey.key]; latestTsAggSubsciptionData[tsKey.key] = subscriptionData[tsKey.key];
} }
dataAggregator.onData(AggregationType[aggTypeString], {data: latestTsAggSubsciptionData}, true, false, true); aggSubscriptionData[aggTypeString] = latestTsAggSubsciptionData;
} }
dataAggregator.onData(aggSubscriptionData, true, false, true);
} }
} else { } else {
this.onData(null, subscriptionData, dataKeyType, dataIndex, true, this.onData(subscriptionData, dataKeyType, dataIndex, true,
this.entityDataSubscriptionOptions.type === widgetType.timeseries, dataUpdatedCb); this.entityDataSubscriptionOptions.type === widgetType.timeseries, false, dataUpdatedCb);
} }
} }
} }
@ -824,89 +834,99 @@ export class EntityDataSubscription {
const subscriptionData = this.toSubscriptionData(entityData.timeseries, true); const subscriptionData = this.toSubscriptionData(entityData.timeseries, true);
if (this.dataAggregators && this.dataAggregators[dataIndex]) { if (this.dataAggregators && this.dataAggregators[dataIndex]) {
const dataAggregator = this.dataAggregators[dataIndex]; const dataAggregator = this.dataAggregators[dataIndex];
const aggSubscriptionData: AggSubscriptionData = {};
aggSubscriptionData[this.subsTw.aggregation.type] = subscriptionData;
let prevDataCb; let prevDataCb;
if (!isUpdate) { if (!isUpdate) {
prevDataCb = dataAggregator.updateOnDataCb((aggType, data, detectChanges) => { prevDataCb = dataAggregator.updateOnDataCb((data, detectChanges) => {
this.onData(null, data, this.datasourceType === DatasourceType.function ? this.onAggData(data, this.datasourceType === DatasourceType.function ?
DataKeyType.function : DataKeyType.timeseries, dataIndex, detectChanges, false, dataUpdatedCb); DataKeyType.function : DataKeyType.timeseries, dataIndex, detectChanges, false, false, dataUpdatedCb);
}); });
} }
dataAggregator.onData(this.subsTw.aggregation.type, {data: subscriptionData}, false, this.history, true); dataAggregator.onData(aggSubscriptionData, false, this.history, true);
if (prevDataCb) { if (prevDataCb) {
dataAggregator.updateOnDataCb(prevDataCb); dataAggregator.updateOnDataCb(prevDataCb);
} }
} else if (!this.history && !isUpdate) { } else if (!this.history && !isUpdate) {
this.onData(null, this.onData(subscriptionData, DataKeyType.timeseries, dataIndex, true, false, false, dataUpdatedCb);
subscriptionData, DataKeyType.timeseries, dataIndex, true, false, dataUpdatedCb);
} }
} }
} }
private onData(aggType: AggregationType | undefined | null, private onData(sourceData: SubscriptionData, type: DataKeyType, dataIndex: number, detectChanges: boolean,
sourceData: SubscriptionData, type: DataKeyType, dataIndex: number, detectChanges: boolean, isTsLatest: boolean, isAggLatest: boolean, dataUpdatedCb: DataUpdatedCb) {
isTsLatest: boolean, dataUpdatedCb: DataUpdatedCb) { const aggSubscriptionData: AggSubscriptionData = {};
for (const keyName of Object.keys(sourceData)) { aggSubscriptionData[AggregationType.NONE] = sourceData;
const keyData = sourceData[keyName]; this.onAggData(aggSubscriptionData, type, dataIndex, detectChanges, isTsLatest, isAggLatest, dataUpdatedCb);
const aggSuffix = aggType && aggType !== AggregationType.NONE ? `_${aggType.toLowerCase()}` : ''; }
const key = `${keyName}_${type}${aggSuffix}${isTsLatest ? '_latest' : ''}`;
const dataKeyList = this.dataKeys[key] as Array<SubscriptionDataKey>; private onAggData(sourceData: AggSubscriptionData, type: DataKeyType, dataIndex: number, detectChanges: boolean,
for (let keyIndex = 0; dataKeyList && keyIndex < dataKeyList.length; keyIndex++) { isTsLatest: boolean, isAggLatest: boolean, dataUpdatedCb: DataUpdatedCb) {
const datasourceKey = `${key}_${keyIndex}`; for (const aggTypeString of Object.keys(sourceData)) {
if (this.datasourceData[dataIndex][datasourceKey].data) { const aggType = AggregationType[aggTypeString];
const dataKey = dataKeyList[keyIndex]; const aggSuffix = isAggLatest ? (aggType !== AggregationType.NONE ? `_${aggType.toLowerCase()}` : '') : '';
const data: DataSet = []; for (const keyName of Object.keys(sourceData[aggType])) {
let prevSeries: [number, any]; const keyData = sourceData[aggType][keyName];
let prevOrigSeries: [number, any]; const key = `${keyName}_${type}${aggSuffix}${isTsLatest ? '_latest' : ''}`;
let datasourceKeyData: DataSet; const dataKeyList = this.dataKeys[key] as Array<SubscriptionDataKey>;
let datasourceOrigKeyData: DataSet; for (let keyIndex = 0; dataKeyList && keyIndex < dataKeyList.length; keyIndex++) {
let update = false; const datasourceKey = `${key}_${keyIndex}`;
if (this.realtime && !isTsLatest) { if (this.datasourceData[dataIndex][datasourceKey].data) {
datasourceKeyData = []; const dataKey = dataKeyList[keyIndex];
datasourceOrigKeyData = []; const data: DataSet = [];
} else { let prevSeries: [number, any];
datasourceKeyData = this.datasourceData[dataIndex][datasourceKey].data; let prevOrigSeries: [number, any];
datasourceOrigKeyData = this.datasourceOrigData[dataIndex][datasourceKey].data; let datasourceKeyData: DataSet;
} let datasourceOrigKeyData: DataSet;
if (datasourceKeyData.length > 0) { let update = false;
prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; if (this.realtime && !isTsLatest) {
prevOrigSeries = datasourceOrigKeyData[datasourceOrigKeyData.length - 1]; datasourceKeyData = [];
} else { datasourceOrigKeyData = [];
prevSeries = [0, 0]; } else {
prevOrigSeries = [0, 0]; datasourceKeyData = this.datasourceData[dataIndex][datasourceKey].data;
} datasourceOrigKeyData = this.datasourceOrigData[dataIndex][datasourceKey].data;
this.datasourceOrigData[dataIndex][datasourceKey].data = []; }
if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && !isTsLatest) { if (datasourceKeyData.length > 0) {
keyData.forEach((keySeries) => { prevSeries = datasourceKeyData[datasourceKeyData.length - 1];
let series = keySeries; prevOrigSeries = datasourceOrigKeyData[datasourceOrigKeyData.length - 1];
const time = series[0]; } else {
this.datasourceOrigData[dataIndex][datasourceKey].data.push([series[0], series[1]]); prevSeries = [0, 0];
let value = EntityDataSubscription.convertValue(series[1]); prevOrigSeries = [0, 0];
if (dataKey.postFunc) { }
value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); this.datasourceOrigData[dataIndex][datasourceKey].data = [];
} if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && !isTsLatest) {
prevOrigSeries = [series[0], series[1]]; keyData.forEach((keySeries) => {
series = [series[0], value]; let series = keySeries;
data.push([series[0], series[1]]); const time = series[0];
prevSeries = [series[0], series[1]]; this.datasourceOrigData[dataIndex][datasourceKey].data.push([series[0], series[1]]);
}); let value = EntityDataSubscription.convertValue(series[1]);
update = true; if (dataKey.postFunc) {
} else if (this.entityDataSubscriptionOptions.type === widgetType.latest || isTsLatest) { value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]);
if (keyData.length > 0) { }
let series = keyData[0]; prevOrigSeries = [series[0], series[1]];
const time = series[0]; series = [series[0], value];
this.datasourceOrigData[dataIndex][datasourceKey].data.push([series[0], series[1]]); data.push([series[0], series[1]]);
let value = EntityDataSubscription.convertValue(series[1]); prevSeries = [series[0], series[1]];
if (dataKey.postFunc) { });
value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); update = true;
} else if (this.entityDataSubscriptionOptions.type === widgetType.latest || isTsLatest) {
if (keyData.length > 0) {
let series = keyData[0];
const time = series[0];
this.datasourceOrigData[dataIndex][datasourceKey].data.push([series[0], series[1]]);
let value = EntityDataSubscription.convertValue(series[1]);
if (dataKey.postFunc) {
value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]);
}
series = [time, value];
data.push([series[0], series[1]]);
} }
series = [time, value]; update = true;
data.push([series[0], series[1]]); }
if (update) {
this.datasourceData[dataIndex][datasourceKey].data = data;
dataUpdatedCb(this.datasourceData[dataIndex][datasourceKey], dataIndex, dataKey.index, detectChanges, isTsLatest);
} }
update = true;
}
if (update) {
this.datasourceData[dataIndex][datasourceKey].data = data;
dataUpdatedCb(this.datasourceData[dataIndex][datasourceKey], dataIndex, dataKey.index, detectChanges, isTsLatest);
} }
} }
} }
@ -934,18 +954,16 @@ export class EntityDataSubscription {
private createRealtimeDataAggregator(subsTw: SubscriptionTimewindow, private createRealtimeDataAggregator(subsTw: SubscriptionTimewindow,
tsKeys: Array<AggKey>, tsKeys: Array<AggKey>,
isLatestDataAgg: boolean, isLatestDataAgg: boolean,
isFloatingLatestDataAgg: boolean,
dataKeyType: DataKeyType, dataKeyType: DataKeyType,
dataIndex: number, dataIndex: number,
dataUpdatedCb: DataUpdatedCb): DataAggregator { dataUpdatedCb: DataUpdatedCb): DataAggregator {
return new DataAggregator( return new DataAggregator(
(aggType, data, detectChanges) => { (data, detectChanges) => {
this.onData(isLatestDataAgg ? aggType : null, data, dataKeyType, dataIndex, detectChanges, this.onAggData(data, dataKeyType, dataIndex, detectChanges,
isLatestDataAgg && (this.entityDataSubscriptionOptions.type === widgetType.timeseries), dataUpdatedCb); isLatestDataAgg && (this.entityDataSubscriptionOptions.type === widgetType.timeseries), isLatestDataAgg, dataUpdatedCb);
}, },
tsKeys, tsKeys,
isLatestDataAgg, isLatestDataAgg,
isFloatingLatestDataAgg,
subsTw, subsTw,
this.utils, this.utils,
this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick
@ -1030,9 +1048,9 @@ export class EntityDataSubscription {
let startTime: number; let startTime: number;
let endTime: number; let endTime: number;
let delta: number; let delta: number;
const generatedData: SubscriptionDataHolder = { const aggType = this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type;
data: {} const generatedData: AggSubscriptionData = {};
}; generatedData[aggType] = {};
if (!this.history) { if (!this.history) {
delta = Math.floor(this.tickElapsed / this.frequency); delta = Math.floor(this.tickElapsed / this.frequency);
} }
@ -1065,11 +1083,10 @@ export class EntityDataSubscription {
endTime = Math.min(currentTime, endTime); endTime = Math.min(currentTime, endTime);
} }
} }
generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, startTime, endTime); generatedData[aggType][`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, startTime, endTime);
} }
if (this.dataAggregators && this.dataAggregators.length) { if (this.dataAggregators && this.dataAggregators.length) {
this.dataAggregators[0].onData(this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type, this.dataAggregators[0].onData(generatedData, true, this.history, detectChanges);
generatedData, true, this.history, detectChanges);
} }
if (!this.history) { if (!this.history) {

4
ui-ngx/src/app/core/services/dashboard-utils.service.ts

@ -34,6 +34,7 @@ import { Datasource, DatasourceType, Widget, widgetType } from '@app/shared/mode
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models'; import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models';
import { EntityId } from '@app/shared/models/id/entity-id'; import { EntityId } from '@app/shared/models/id/entity-id';
import { initModelFromDefaultTimewindow } from '@shared/models/time/time.models';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -215,6 +216,9 @@ export class DashboardUtilsService {
delete datasource.deviceAliasId; delete datasource.deviceAliasId;
} }
}); });
if (widget.type === widgetType.latest) {
widget.config.timewindow = initModelFromDefaultTimewindow(widget.config.timewindow, true, this.timeService);
}
// Temp workaround // Temp workaround
if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') { if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') {
widget.typeAlias = 'basic_timeseries'; widget.typeAlias = 'basic_timeseries';

3
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -1062,7 +1062,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
dataKeys: config.alarmSource.dataKeys || [] dataKeys: config.alarmSource.dataKeys || []
}; };
} }
const newWidget: Widget = { let newWidget: Widget = {
isSystemType: widget.isSystemType, isSystemType: widget.isSystemType,
bundleAlias: widget.bundleAlias, bundleAlias: widget.bundleAlias,
typeAlias: widgetTypeInfo.alias, typeAlias: widgetTypeInfo.alias,
@ -1076,6 +1076,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
row: 0, row: 0,
col: 0 col: 0
}; };
newWidget = this.dashboardUtils.validateAndUpdateWidget(newWidget);
if (widgetTypeInfo.typeParameters.useCustomDatasources) { if (widgetTypeInfo.typeParameters.useCustomDatasources) {
this.addWidgetToDashboard(newWidget); this.addWidgetToDashboard(newWidget);
} else { } else {

20
ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts

@ -18,12 +18,7 @@ import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@an
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { import { DataKey, dataKeyAggregationTypeHintTranslationMap, Widget, widgetType } from '@shared/models/widget.models';
DataKey,
dataKeyAggregationTypeHintTranslationMap,
Widget,
widgetType
} from '@shared/models/widget.models';
import { import {
ControlValueAccessor, ControlValueAccessor,
FormBuilder, FormBuilder,
@ -184,6 +179,19 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
postFuncBody: [null, []] postFuncBody: [null, []]
}); });
this.dataKeyFormGroup.get('aggregationType').valueChanges.subscribe(
(aggType) => {
if (!this.dataKeyFormGroup.get('label').dirty) {
let newLabel = this.dataKeyFormGroup.get('name').value;
if (aggType !== AggregationType.NONE) {
const prefix = this.translate.instant(aggregationTranslations.get(aggType));
newLabel = prefix + ' ' + newLabel;
}
this.dataKeyFormGroup.get('label').patchValue(newLabel);
}
}
);
this.dataKeyFormGroup.valueChanges.subscribe(() => { this.dataKeyFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();
}); });

3
ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts

@ -110,6 +110,7 @@ import { DatePipe } from '@angular/common';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { hidePageSizePixelValue } from '@shared/models/constants'; import { hidePageSizePixelValue } from '@shared/models/constants';
import { AggregationType } from '@shared/models/time/time.models';
interface EntitiesTableWidgetSettings extends TableWidgetSettings { interface EntitiesTableWidgetSettings extends TableWidgetSettings {
entitiesTitle: string; entitiesTitle: string;
@ -428,7 +429,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
dataKey.label = this.utils.customTranslation(dataKey.label, dataKey.label); dataKey.label = this.utils.customTranslation(dataKey.label, dataKey.label);
dataKey.title = dataKey.label; dataKey.title = dataKey.label;
dataKey.def = 'def' + this.columns.length; dataKey.def = 'def' + this.columns.length;
dataKey.sortable = !dataKey.usePostProcessing; dataKey.sortable = !dataKey.usePostProcessing && (!dataKey.aggregationType || dataKey.aggregationType === AggregationType.NONE);
const keySettings: TableWidgetDataKeySettings = dataKey.settings; const keySettings: TableWidgetDataKeySettings = dataKey.settings;
if (dataKey.type === DataKeyType.entityField && if (dataKey.type === DataKeyType.entityField &&
!isDefined(keySettings.columnWidth) || keySettings.columnWidth === '0px') { !isDefined(keySettings.columnWidth) || keySettings.columnWidth === '0px') {

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

@ -34,6 +34,7 @@
style="padding-right: 8px;">widget-config.timewindow</span> style="padding-right: 8px;">widget-config.timewindow</span>
<tb-timewindow asButton="true" <tb-timewindow asButton="true"
isEdit="true" isEdit="true"
quickIntervalOnly="{{ widgetType === widgetTypes.latest }}"
aggregation="{{ widgetType === widgetTypes.timeseries }}" aggregation="{{ widgetType === widgetTypes.timeseries }}"
fxFlex formControlName="timewindow"></tb-timewindow> fxFlex formControlName="timewindow"></tb-timewindow>
</section> </section>

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

@ -46,6 +46,7 @@
</span> </span>
<tb-timewindow *ngIf="widget.hasTimewindow" <tb-timewindow *ngIf="widget.hasTimewindow"
aggregation="{{widget.hasAggregation}}" aggregation="{{widget.hasAggregation}}"
quickIntervalOnly="{{widget.onlyQuickInterval}}"
timezone="true" timezone="true"
[isEdit]="isEdit" [isEdit]="isEdit"
[(ngModel)]="widgetComponent.widget.config.timewindow" [(ngModel)]="widgetComponent.widget.config.timewindow"

6
ui-ngx/src/app/modules/home/models/dashboard-component.models.ts

@ -333,6 +333,8 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
hasAggregation: boolean; hasAggregation: boolean;
onlyQuickInterval: boolean;
style: {[klass: string]: any}; style: {[klass: string]: any};
showWidgetTitlePanel: boolean; showWidgetTitlePanel: boolean;
@ -429,10 +431,12 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
this.enableFullscreen = isDefined(this.widget.config.enableFullscreen) ? this.widget.config.enableFullscreen : true; this.enableFullscreen = isDefined(this.widget.config.enableFullscreen) ? this.widget.config.enableFullscreen : true;
let canHaveTimewindow = false; let canHaveTimewindow = false;
let onlyQuickInterval = false;
if (this.widget.type === widgetType.timeseries || this.widget.type === widgetType.alarm) { if (this.widget.type === widgetType.timeseries || this.widget.type === widgetType.alarm) {
canHaveTimewindow = true; canHaveTimewindow = true;
} else if (this.widget.type === widgetType.latest) { } else if (this.widget.type === widgetType.latest) {
canHaveTimewindow = datasourcesHasAggregation(this.widget.config.datasources); canHaveTimewindow = datasourcesHasAggregation(this.widget.config.datasources);
onlyQuickInterval = canHaveTimewindow;
} }
this.hasTimewindow = canHaveTimewindow ? this.hasTimewindow = canHaveTimewindow ?
@ -441,6 +445,8 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
|| this.widget.config.displayTimewindow)) : false) || this.widget.config.displayTimewindow)) : false)
: false; : false;
this.onlyQuickInterval = onlyQuickInterval;
this.hasAggregation = this.widget.type === widgetType.timeseries; this.hasAggregation = this.widget.type === widgetType.timeseries;
this.style = { this.style = {

25
ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2022 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 '../../../../scss/constants';
:host {
min-width: 355px;
@media #{$mat-xs} {
min-width: 0;
width: 100%;
}
}

1
ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts

@ -21,6 +21,7 @@ import { QuickTimeInterval, QuickTimeIntervalTranslationMap } from '@shared/mode
@Component({ @Component({
selector: 'tb-quick-time-interval', selector: 'tb-quick-time-interval',
templateUrl: './quick-time-interval.component.html', templateUrl: './quick-time-interval.component.html',
styleUrls: ['./quick-time-interval.component.scss'],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,

63
ui-ngx/src/app/shared/components/time/timewindow-panel.component.html

@ -29,32 +29,57 @@
</section> </section>
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval"> <section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval">
<div formGroupName="realtime" class="mat-content mat-padding" style="padding-top: 8px;"> <div formGroupName="realtime" class="mat-content mat-padding" style="padding-top: 8px;">
<mat-radio-group formControlName="realtimeType"> <mat-radio-group *ngIf="!quickIntervalOnly" [fxShow]="isEdit || (!timewindow.hideLastInterval && !timewindow.hideQuickInterval)"
formControlName="realtimeType">
<mat-radio-button [value]="realtimeTypes.LAST_INTERVAL" color="primary"> <mat-radio-button [value]="realtimeTypes.LAST_INTERVAL" color="primary">
<section fxLayout="column"> <section fxLayout="row">
<span translate>timewindow.last</span> <section *ngIf="isEdit" fxLayout="column" style="padding-right: 8px;">
<tb-timeinterval <label class="tb-small hide-label" translate>timewindow.hide</label>
formControlName="timewindowMs" <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideLastInterval"
predefinedName="timewindow.last" (ngModelChange)="onHideLastIntervalChanged()"></mat-checkbox>
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL" </section>
[required]="timewindow.selectedTab === timewindowTypes.REALTIME && <section fxLayout="column">
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL" <span translate>timewindow.last</span>
style="padding-top: 8px;"></tb-timeinterval> <tb-timeinterval
formControlName="timewindowMs"
predefinedName="timewindow.last"
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL"
[required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL"
style="padding-top: 8px;"></tb-timeinterval>
</section>
</section> </section>
</mat-radio-button> </mat-radio-button>
<mat-radio-button [value]="realtimeTypes.INTERVAL" color="primary"> <mat-radio-button [value]="realtimeTypes.INTERVAL" color="primary">
<section fxLayout="column"> <section fxLayout="row">
<span translate>timewindow.interval</span> <section *ngIf="isEdit" fxLayout="column" style="padding-right: 8px;">
<tb-quick-time-interval <label class="tb-small hide-label" translate>timewindow.hide</label>
formControlName="quickInterval" <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideQuickInterval"
onlyCurrentInterval="true" (ngModelChange)="onHideQuickIntervalChanged()"></mat-checkbox>
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" </section>
[required]="timewindow.selectedTab === timewindowTypes.REALTIME && <section fxLayout="column">
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" <span translate>timewindow.interval</span>
style="padding-top: 8px"></tb-quick-time-interval> <tb-quick-time-interval
formControlName="quickInterval"
onlyCurrentInterval="true"
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
[required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
style="padding-top: 8px"></tb-quick-time-interval>
</section>
</section> </section>
</mat-radio-button> </mat-radio-button>
</mat-radio-group> </mat-radio-group>
<tb-timeinterval *ngIf="!isEdit && !timewindow.hideLastInterval && timewindow.hideQuickInterval"
formControlName="timewindowMs"
predefinedName="timewindow.last"
required
style="padding-top: 8px;"></tb-timeinterval>
<tb-quick-time-interval *ngIf="quickIntervalOnly || !isEdit && timewindow.hideLastInterval && !timewindow.hideQuickInterval"
formControlName="quickInterval"
onlyCurrentInterval="true"
required
style="padding-top: 8px"></tb-quick-time-interval>
</div> </div>
</section> </section>
</section> </section>

53
ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts

@ -36,6 +36,7 @@ export const TIMEWINDOW_PANEL_DATA = new InjectionToken<any>('TimewindowPanelDat
export interface TimewindowPanelData { export interface TimewindowPanelData {
historyOnly: boolean; historyOnly: boolean;
quickIntervalOnly: boolean;
timewindow: Timewindow; timewindow: Timewindow;
aggregation: boolean; aggregation: boolean;
timezone: boolean; timezone: boolean;
@ -51,6 +52,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
historyOnly = false; historyOnly = false;
quickIntervalOnly = false;
aggregation = false; aggregation = false;
timezone = false; timezone = false;
@ -83,6 +86,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
public viewContainerRef: ViewContainerRef) { public viewContainerRef: ViewContainerRef) {
super(store); super(store);
this.historyOnly = data.historyOnly; this.historyOnly = data.historyOnly;
this.quickIntervalOnly = data.quickIntervalOnly;
this.timewindow = data.timewindow; this.timewindow = data.timewindow;
this.aggregation = data.aggregation; this.aggregation = data.aggregation;
this.timezone = data.timezone; this.timezone = data.timezone;
@ -91,6 +95,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
const hideInterval = this.timewindow.hideInterval || false; const hideInterval = this.timewindow.hideInterval || false;
const hideLastInterval = this.timewindow.hideLastInterval || false;
const hideQuickInterval = this.timewindow.hideQuickInterval || false;
const hideAggregation = this.timewindow.hideAggregation || false; const hideAggregation = this.timewindow.hideAggregation || false;
const hideAggInterval = this.timewindow.hideAggInterval || false; const hideAggInterval = this.timewindow.hideAggInterval || false;
const hideTimezone = this.timewindow.hideTimezone || false; const hideTimezone = this.timewindow.hideTimezone || false;
@ -103,10 +109,11 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL, ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL,
disabled: hideInterval disabled: hideInterval
}), }),
timewindowMs: [ timewindowMs: this.fb.control({
this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined' value: this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined'
? this.timewindow.realtime.timewindowMs : null ? this.timewindow.realtime.timewindowMs : null,
], disabled: hideInterval || hideLastInterval
}),
interval: [ interval: [
this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined' this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined'
? this.timewindow.realtime.interval : null ? this.timewindow.realtime.interval : null
@ -114,7 +121,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
quickInterval: this.fb.control({ quickInterval: this.fb.control({
value: this.timewindow.realtime && typeof this.timewindow.realtime.quickInterval !== 'undefined' value: this.timewindow.realtime && typeof this.timewindow.realtime.quickInterval !== 'undefined'
? this.timewindow.realtime.quickInterval : null, ? this.timewindow.realtime.quickInterval : null,
disabled: hideInterval disabled: hideInterval || hideQuickInterval
}) })
} }
), ),
@ -289,8 +296,40 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
this.timewindowForm.get('history.fixedTimewindow').enable({emitEvent: false}); this.timewindowForm.get('history.fixedTimewindow').enable({emitEvent: false});
this.timewindowForm.get('history.quickInterval').enable({emitEvent: false}); this.timewindowForm.get('history.quickInterval').enable({emitEvent: false});
this.timewindowForm.get('realtime.realtimeType').enable({emitEvent: false}); this.timewindowForm.get('realtime.realtimeType').enable({emitEvent: false});
this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false}); if (!this.timewindow.hideLastInterval) {
this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false}); this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false});
}
if (!this.timewindow.hideQuickInterval) {
this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false});
}
}
this.timewindowForm.markAsDirty();
}
onHideLastIntervalChanged() {
if (this.timewindow.hideLastInterval) {
this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false});
if (!this.timewindow.hideQuickInterval) {
this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.INTERVAL);
}
} else {
if (!this.timewindow.hideInterval) {
this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false});
}
}
this.timewindowForm.markAsDirty();
}
onHideQuickIntervalChanged() {
if (this.timewindow.hideQuickInterval) {
this.timewindowForm.get('realtime.quickInterval').disable({emitEvent: false});
if (!this.timewindow.hideLastInterval) {
this.timewindowForm.get('realtime.realtimeType').setValue(RealtimeWindowType.LAST_INTERVAL);
}
} else {
if (!this.timewindow.hideInterval) {
this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false});
}
} }
this.timewindowForm.markAsDirty(); this.timewindowForm.markAsDirty();
} }

14
ui-ngx/src/app/shared/components/time/timewindow.component.ts

@ -82,6 +82,17 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
return this.historyOnlyValue; return this.historyOnlyValue;
} }
quickIntervalOnlyValue = false;
@Input()
set quickIntervalOnly(val) {
this.quickIntervalOnlyValue = coerceBooleanProperty(val);
}
get quickIntervalOnly() {
return this.quickIntervalOnlyValue;
}
aggregationValue = false; aggregationValue = false;
@Input() @Input()
@ -240,6 +251,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
{ {
timewindow: deepClone(this.innerValue), timewindow: deepClone(this.innerValue),
historyOnly: this.historyOnly, historyOnly: this.historyOnly,
quickIntervalOnly: this.quickIntervalOnly,
aggregation: this.aggregation, aggregation: this.aggregation,
timezone: this.timezone, timezone: this.timezone,
isEdit: this.isEdit isEdit: this.isEdit
@ -278,7 +290,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
} }
writeValue(obj: Timewindow): void { writeValue(obj: Timewindow): void {
this.innerValue = initModelFromDefaultTimewindow(obj, this.timeService); this.innerValue = initModelFromDefaultTimewindow(obj, this.quickIntervalOnly, this.timeService);
this.timewindowDisabled = this.isTimewindowDisabled(); this.timewindowDisabled = this.isTimewindowDisabled();
this.updateDisplayValue(); this.updateDisplayValue();
} }

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

@ -768,7 +768,6 @@ export interface EntityData {
latest: {[entityKeyType: string]: {[key: string]: TsValue}}; latest: {[entityKeyType: string]: {[key: string]: TsValue}};
timeseries: {[key: string]: Array<TsValue>}; timeseries: {[key: string]: Array<TsValue>};
aggLatest?: {[aggType: string]: {[key: string]: TsValue}}; aggLatest?: {[aggType: string]: {[key: string]: TsValue}};
aggFloating?: {[key: string]: Array<TsValue>};
} }
export interface AlarmData extends AlarmInfo { export interface AlarmData extends AlarmInfo {

5
ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts

@ -188,7 +188,6 @@ export interface AggTimeSeriesCmd {
keys: Array<AggKey>; keys: Array<AggKey>;
startTs: number; startTs: number;
timeWindow: number; timeWindow: number;
floating: boolean;
} }
export class EntityDataCmd implements WebsocketCmd { export class EntityDataCmd implements WebsocketCmd {
@ -315,6 +314,10 @@ export interface SubscriptionData {
[key: string]: [number, any, number?][]; [key: string]: [number, any, number?][];
} }
export interface AggSubscriptionData {
[aggType: string]: SubscriptionData;
}
export interface SubscriptionDataHolder { export interface SubscriptionDataHolder {
data: SubscriptionData; data: SubscriptionData;
} }

15
ui-ngx/src/app/shared/models/time/time.models.ts

@ -96,6 +96,8 @@ export interface Timewindow {
displayValue?: string; displayValue?: string;
displayTimezoneAbbr?: string; displayTimezoneAbbr?: string;
hideInterval?: boolean; hideInterval?: boolean;
hideQuickInterval?: boolean;
hideLastInterval?: boolean;
hideAggregation?: boolean; hideAggregation?: boolean;
hideAggInterval?: boolean; hideAggInterval?: boolean;
hideTimezone?: boolean; hideTimezone?: boolean;
@ -188,6 +190,8 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
return { return {
displayValue: '', displayValue: '',
hideInterval: false, hideInterval: false,
hideLastInterval: false,
hideQuickInterval: false,
hideAggregation: false, hideAggregation: false,
hideAggInterval: false, hideAggInterval: false,
hideTimezone: false, hideTimezone: false,
@ -223,10 +227,12 @@ function getTimewindowType(timewindow: Timewindow): TimewindowType {
} }
} }
export function initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow { export function initModelFromDefaultTimewindow(value: Timewindow, quickIntervalOnly: boolean, timeService: TimeService): Timewindow {
const model = defaultTimewindow(timeService); const model = defaultTimewindow(timeService);
if (value) { if (value) {
model.hideInterval = value.hideInterval; model.hideInterval = value.hideInterval;
model.hideLastInterval = value.hideLastInterval;
model.hideQuickInterval = value.hideQuickInterval;
model.hideAggregation = value.hideAggregation; model.hideAggregation = value.hideAggregation;
model.hideAggInterval = value.hideAggInterval; model.hideAggInterval = value.hideAggInterval;
model.hideTimezone = value.hideTimezone; model.hideTimezone = value.hideTimezone;
@ -281,6 +287,9 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
} }
model.timezone = value.timezone; model.timezone = value.timezone;
} }
if (quickIntervalOnly) {
model.realtime.realtimeType = RealtimeWindowType.INTERVAL;
}
return model; return model;
} }
@ -304,6 +313,8 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
} }
return { return {
hideInterval: timewindow.hideInterval || false, hideInterval: timewindow.hideInterval || false,
hideLastInterval: timewindow.hideLastInterval || false,
hideQuickInterval: timewindow.hideQuickInterval || false,
hideAggregation: timewindow.hideAggregation || false, hideAggregation: timewindow.hideAggregation || false,
hideAggInterval: timewindow.hideAggInterval || false, hideAggInterval: timewindow.hideAggInterval || false,
hideTimezone: timewindow.hideTimezone || false, hideTimezone: timewindow.hideTimezone || false,
@ -694,6 +705,8 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti
export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
const cloned: Timewindow = {}; const cloned: Timewindow = {};
cloned.hideInterval = timewindow.hideInterval || false; cloned.hideInterval = timewindow.hideInterval || false;
cloned.hideLastInterval = timewindow.hideLastInterval || false;
cloned.hideQuickInterval = timewindow.hideQuickInterval || false;
cloned.hideAggregation = timewindow.hideAggregation || false; cloned.hideAggregation = timewindow.hideAggregation || false;
cloned.hideAggInterval = timewindow.hideAggInterval || false; cloned.hideAggInterval = timewindow.hideAggInterval || false;
cloned.hideTimezone = timewindow.hideTimezone || false; cloned.hideTimezone = timewindow.hideTimezone || false;

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

@ -338,7 +338,7 @@ export interface Datasource {
export function datasourcesHasAggregation(datasources?: Array<Datasource>): boolean { export function datasourcesHasAggregation(datasources?: Array<Datasource>): boolean {
if (datasources) { if (datasources) {
const foundDatasource = datasources.find(datasource => { const foundDatasource = datasources.find(datasource => {
const found = datasource.dataKeys.find(key => key.type === DataKeyType.timeseries && const found = datasource.dataKeys && datasource.dataKeys.find(key => key.type === DataKeyType.timeseries &&
key.aggregationType && key.aggregationType !== AggregationType.NONE); key.aggregationType && key.aggregationType !== AggregationType.NONE);
return !!found; return !!found;
}); });

Loading…
Cancel
Save