Browse Source

Add timezone support for timewindow

pull/4259/head
Igor Kulikov 6 years ago
parent
commit
3ed49e3f02
  1. 79
      ui-ngx/src/app/core/api/data-aggregator.ts
  2. 39
      ui-ngx/src/app/core/api/entity-data-subscription.ts
  3. 30
      ui-ngx/src/app/core/api/widget-subscription.ts
  4. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  5. 1
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html
  6. 12
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  7. 9
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts
  8. 14
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.html
  9. 26
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts
  10. 14
      ui-ngx/src/app/shared/components/time/timewindow.component.ts
  11. 57
      ui-ngx/src/app/shared/components/time/timezone-select.component.ts
  12. 41
      ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
  13. 250
      ui-ngx/src/app/shared/models/time/time.models.ts

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

@ -18,8 +18,8 @@ import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/tel
import { import {
AggregationType, AggregationType,
calculateIntervalEndTime, calculateIntervalEndTime,
calculateIntervalStartTime, calculateIntervalStartTime, getCurrentTime,
QuickTimeInterval QuickTimeInterval, SubscriptionTimewindow
} from '@shared/models/time/time.models'; } from '@shared/models/time/time.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { deepClone } from '@core/utils'; import { deepClone } from '@core/utils';
@ -78,34 +78,29 @@ export class DataAggregator {
private resetPending = false; private resetPending = false;
private updatedData = false; private updatedData = false;
private noAggregation = this.aggregationType === AggregationType.NONE; private noAggregation = this.subsTw.aggregation.type === AggregationType.NONE;
private aggregationTimeout = Math.max(this.interval, 1000); private aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
private readonly aggFunction: AggFunction; private readonly aggFunction: AggFunction;
private intervalTimeoutHandle: Timeout; private intervalTimeoutHandle: Timeout;
private intervalScheduledTime: number; private intervalScheduledTime: number;
private startTs = this.subsTw.startTs + this.subsTw.tsOffset;
private endTs: number; private endTs: number;
private elapsed: number; private elapsed: number;
constructor(private onDataCb: onAggregatedData, constructor(private onDataCb: onAggregatedData,
private tsKeyNames: string[], private tsKeyNames: string[],
private startTs: number, private subsTw: SubscriptionTimewindow,
private limit: number,
private aggregationType: AggregationType,
private timeWindow: number,
private interval: number,
private stateData: boolean,
private utils: UtilsService, private utils: UtilsService,
private ignoreDataUpdateOnIntervalTick: boolean, private ignoreDataUpdateOnIntervalTick: boolean) {
private quickInterval: QuickTimeInterval) {
this.tsKeyNames.forEach((key) => { this.tsKeyNames.forEach((key) => {
this.dataBuffer[key] = []; this.dataBuffer[key] = [];
}); });
if (this.stateData) { if (this.subsTw.aggregation.stateData) {
this.lastPrevKvPairData = {}; this.lastPrevKvPairData = {};
} }
switch (this.aggregationType) { switch (this.subsTw.aggregation.type) {
case AggregationType.MIN: case AggregationType.MIN:
this.aggFunction = min; this.aggFunction = min;
break; break;
@ -135,19 +130,21 @@ export class DataAggregator {
return prevOnDataCb; return prevOnDataCb;
} }
public reset(startTs: number, timeWindow: number, interval: number) { public reset(subsTw: SubscriptionTimewindow) {
if (this.intervalTimeoutHandle) { if (this.intervalTimeoutHandle) {
clearTimeout(this.intervalTimeoutHandle); clearTimeout(this.intervalTimeoutHandle);
this.intervalTimeoutHandle = null; this.intervalTimeoutHandle = null;
} }
this.subsTw = subsTw;
this.intervalScheduledTime = this.utils.currentPerfTime(); this.intervalScheduledTime = this.utils.currentPerfTime();
this.startTs = startTs; this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;
this.timeWindow = timeWindow; if (this.subsTw.quickInterval) {
this.interval = interval; this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;
const endTs = this.startTs + this.timeWindow; } else {
this.endTs = calculateIntervalEndTime(this.quickInterval, endTs); this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
}
this.elapsed = 0; this.elapsed = 0;
this.aggregationTimeout = Math.max(this.interval, 1000); this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
this.resetPending = true; this.resetPending = true;
this.updatedData = false; this.updatedData = false;
this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout);
@ -168,8 +165,11 @@ export class DataAggregator {
if (!this.dataReceived) { if (!this.dataReceived) {
this.elapsed = 0; this.elapsed = 0;
this.dataReceived = true; this.dataReceived = true;
const endTs = this.startTs + this.timeWindow; if (this.subsTw.quickInterval) {
this.endTs = calculateIntervalEndTime(this.quickInterval, endTs); this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;
} else {
this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
}
} }
if (this.resetPending) { if (this.resetPending) {
this.resetPending = false; this.resetPending = false;
@ -203,15 +203,19 @@ export class DataAggregator {
this.intervalTimeoutHandle = null; this.intervalTimeoutHandle = null;
} }
if (!history) { if (!history) {
const delta = Math.floor(this.elapsed / this.interval); const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval);
if (delta || !this.data) { if (delta || !this.data) {
const tickTs = delta * this.interval; const tickTs = delta * this.subsTw.aggregation.interval;
const startTS = this.startTs + tickTs; if (this.subsTw.quickInterval) {
this.startTs = calculateIntervalStartTime(this.quickInterval, startTS); const currentDate = getCurrentTime(this.subsTw.timezone);
const endTs = this.endTs + tickTs; this.startTs = calculateIntervalStartTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
this.endTs = calculateIntervalEndTime(this.quickInterval, endTs); this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
} else {
this.startTs += tickTs;
this.endTs += tickTs;
}
this.data = this.updateData(); this.data = this.updateData();
this.elapsed = this.elapsed - delta * this.interval; this.elapsed = this.elapsed - delta * this.subsTw.aggregation.interval;
} }
} else { } else {
this.data = this.updateData(); this.data = this.updateData();
@ -234,7 +238,7 @@ export class DataAggregator {
let keyData = this.dataBuffer[key]; let keyData = this.dataBuffer[key];
aggKeyData.forEach((aggData, aggTimestamp) => { aggKeyData.forEach((aggData, aggTimestamp) => {
if (aggTimestamp <= this.startTs) { if (aggTimestamp <= this.startTs) {
if (this.stateData && if (this.subsTw.aggregation.stateData &&
(!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) {
this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue];
} }
@ -246,11 +250,11 @@ export class DataAggregator {
} }
}); });
keyData.sort((set1, set2) => set1[0] - set2[0]); keyData.sort((set1, set2) => set1[0] - set2[0]);
if (this.stateData) { if (this.subsTw.aggregation.stateData) {
this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key])); this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key]));
} }
if (keyData.length > this.limit) { if (keyData.length > this.subsTw.aggregation.limit) {
keyData = keyData.slice(keyData.length - this.limit); keyData = keyData.slice(keyData.length - this.subsTw.aggregation.limit);
} }
this.dataBuffer[key] = keyData; this.dataBuffer[key] = keyData;
} }
@ -286,7 +290,7 @@ export class DataAggregator {
} }
private processAggregatedData(data: SubscriptionData): AggregationMap { private processAggregatedData(data: SubscriptionData): AggregationMap {
const isCount = this.aggregationType === AggregationType.COUNT; const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
const aggregationMap: AggregationMap = {}; const aggregationMap: AggregationMap = {};
for (const key of Object.keys(data)) { for (const key of Object.keys(data)) {
let aggKeyData = aggregationMap[key]; let aggKeyData = aggregationMap[key];
@ -311,7 +315,7 @@ export class DataAggregator {
} }
private updateAggregatedData(data: SubscriptionData) { private updateAggregatedData(data: SubscriptionData) {
const isCount = this.aggregationType === AggregationType.COUNT; const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
for (const key of Object.keys(data)) { for (const key of Object.keys(data)) {
let aggKeyData = this.aggregationMap[key]; let aggKeyData = this.aggregationMap[key];
if (!aggKeyData) { if (!aggKeyData) {
@ -323,7 +327,8 @@ export class DataAggregator {
const timestamp = kvPair[0]; const timestamp = kvPair[0];
const value = this.convertValue(kvPair[1]); const value = this.convertValue(kvPair[1]);
const aggTimestamp = this.noAggregation ? timestamp : (this.startTs + const aggTimestamp = this.noAggregation ? timestamp : (this.startTs +
Math.floor((timestamp - this.startTs) / this.interval) * this.interval + this.interval / 2); Math.floor((timestamp - this.startTs) / this.subsTw.aggregation.interval) *
this.subsTw.aggregation.interval + this.subsTw.aggregation.interval / 2);
let aggData = aggKeyData.get(aggTimestamp); let aggData = aggKeyData.get(aggTimestamp);
if (!aggData) { if (!aggData) {
aggData = { aggData = {

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

@ -237,7 +237,7 @@ export class EntityDataSubscription {
}; };
if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
this.prepareSubscriptionCommands(this.dataCommand); this.prepareSubscriptionCommands();
} }
this.subscriber.subscriptionCommands.push(this.dataCommand); this.subscriber.subscriptionCommands.push(this.dataCommand);
@ -256,8 +256,8 @@ export class EntityDataSubscription {
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.entityDataSubscriptionOptions.type === widgetType.timeseries &&
!this.history && this.tsFields.length) { !this.history && this.tsFields.length) {
const newSubsTw: SubscriptionTimewindow = this.listener.updateRealtimeSubscription(); const newSubsTw = this.listener.updateRealtimeSubscription();
this.subsTw = newSubsTw; this.subsTw = newSubsTw;
targetCommand.tsCmd.startTs = this.subsTw.startTs; targetCommand.tsCmd.startTs = this.subsTw.startTs;
targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow; targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow;
@ -266,9 +266,10 @@ export class EntityDataSubscription {
targetCommand.tsCmd.agg = this.subsTw.aggregation.type; targetCommand.tsCmd.agg = this.subsTw.aggregation.type;
targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData; targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData;
this.dataAggregators.forEach((dataAggregator) => { this.dataAggregators.forEach((dataAggregator) => {
dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); dataAggregator.reset(newSubsTw);
}); });
} }
this.subscriber.setTsOffset(this.subsTw.tsOffset);
targetCommand.query = this.dataCommand.query; targetCommand.query = this.dataCommand.query;
this.subscriber.subscriptionCommands = [targetCommand]; this.subscriber.subscriptionCommands = [targetCommand];
} else { } else {
@ -393,7 +394,7 @@ export class EntityDataSubscription {
if (this.datasourceType === DatasourceType.entity) { if (this.datasourceType === DatasourceType.entity) {
this.subsCommand = new EntityDataCmd(); this.subsCommand = new EntityDataCmd();
this.subsCommand.cmdId = this.dataCommand.cmdId; this.subsCommand.cmdId = this.dataCommand.cmdId;
this.prepareSubscriptionCommands(this.subsCommand); this.prepareSubscriptionCommands();
if (!this.subsCommand.isEmpty()) { if (!this.subsCommand.isEmpty()) {
this.subscriber.subscriptionCommands = [this.subsCommand]; this.subscriber.subscriptionCommands = [this.subsCommand];
this.subscriber.update(); this.subscriber.update();
@ -404,11 +405,11 @@ export class EntityDataSubscription {
this.started = true; this.started = true;
} }
private prepareSubscriptionCommands(cmd: EntityDataCmd) { private prepareSubscriptionCommands() {
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) {
cmd.historyCmd = { this.subsCommand.historyCmd = {
keys: this.tsFields.map(key => key.key), keys: this.tsFields.map(key => key.key),
startTs: this.subsTw.fixedWindow.startTimeMs, startTs: this.subsTw.fixedWindow.startTimeMs,
endTs: this.subsTw.fixedWindow.endTimeMs, endTs: this.subsTw.fixedWindow.endTimeMs,
@ -418,7 +419,7 @@ export class EntityDataSubscription {
fetchLatestPreviousPoint: this.subsTw.aggregation.stateData fetchLatestPreviousPoint: this.subsTw.aggregation.stateData
}; };
} else { } else {
cmd.tsCmd = { this.subsCommand.tsCmd = {
keys: this.tsFields.map(key => key.key), keys: this.tsFields.map(key => key.key),
startTs: this.subsTw.startTs, startTs: this.subsTw.startTs,
timeWindow: this.subsTw.aggregation.timeWindow, timeWindow: this.subsTw.aggregation.timeWindow,
@ -429,9 +430,10 @@ export class EntityDataSubscription {
}; };
} }
} }
this.subscriber.setTsOffset(this.subsTw.tsOffset);
} else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) {
if (this.latestValues.length > 0) { if (this.latestValues.length > 0) {
cmd.latestCmd = { this.subsCommand.latestCmd = {
keys: this.latestValues keys: this.latestValues
}; };
} }
@ -745,15 +747,9 @@ export class EntityDataSubscription {
this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb); this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb);
}, },
tsKeyNames, tsKeyNames,
subsTw.startTs, subsTw,
subsTw.aggregation.limit,
subsTw.aggregation.type,
subsTw.aggregation.timeWindow,
subsTw.aggregation.interval,
subsTw.aggregation.stateData,
this.utils, this.utils,
this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick, this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick
subsTw.quickInterval
); );
} }
@ -828,7 +824,8 @@ export class EntityDataSubscription {
startTime = dataKey.lastUpdateTime + this.frequency; startTime = dataKey.lastUpdateTime + this.frequency;
endTime = dataKey.lastUpdateTime + deltaElapsed; endTime = dataKey.lastUpdateTime + deltaElapsed;
} else { } else {
startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs; startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs +
this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency;
if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) {
const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit; const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit;
@ -836,8 +833,10 @@ export class EntityDataSubscription {
} }
} }
} else { } else {
startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs; startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs +
endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs; this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs +
this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
} }
} }
generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime); generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime);

30
ui-ngx/src/app/core/api/widget-subscription.ts

@ -40,7 +40,7 @@ import {
calculateIntervalEndTime, calculateIntervalEndTime,
calculateIntervalStartTime, calculateIntervalStartTime,
createSubscriptionTimewindow, createSubscriptionTimewindow,
createTimewindowForComparison, createTimewindowForComparison, getCurrentTime,
SubscriptionTimewindow, SubscriptionTimewindow,
Timewindow, Timewindow,
toHistoryTimewindow, toHistoryTimewindow,
@ -839,9 +839,11 @@ export class WidgetSubscription implements IWidgetSubscription {
if (this.alarmDataListener) { if (this.alarmDataListener) {
this.ctx.alarmDataService.stopSubscription(this.alarmDataListener); this.ctx.alarmDataService.stopSubscription(this.alarmDataListener);
} }
if (this.timeWindowConfig) { if (this.timeWindowConfig) {
this.updateRealtimeSubscription(); this.updateRealtimeSubscription();
} }
this.alarmDataListener = { this.alarmDataListener = {
subscriptionTimewindow: this.subscriptionTimewindow, subscriptionTimewindow: this.subscriptionTimewindow,
alarmSource: this.alarmSource, alarmSource: this.alarmSource,
@ -1082,14 +1084,21 @@ export class WidgetSubscription implements IWidgetSubscription {
private updateTimewindow() { private updateTimewindow() {
this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000;
this.timeWindow.timezone = this.subscriptionTimewindow.timezone;
if (this.subscriptionTimewindow.realtimeWindowMs) { if (this.subscriptionTimewindow.realtimeWindowMs) {
this.timeWindow.maxTime = calculateIntervalEndTime( if (this.subscriptionTimewindow.quickInterval) {
this.subscriptionTimewindow.quickInterval, moment().valueOf() + this.timeWindow.stDiff); const currentDate = getCurrentTime(this.subscriptionTimewindow.timezone);
this.timeWindow.minTime = calculateIntervalStartTime( this.timeWindow.maxTime = calculateIntervalEndTime(
this.subscriptionTimewindow.quickInterval, this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs); this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset;
this.timeWindow.minTime = calculateIntervalStartTime(
this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset;
} else {
this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff;
this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs;
}
} else if (this.subscriptionTimewindow.fixedWindow) { } else if (this.subscriptionTimewindow.fixedWindow) {
this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs; this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs + this.subscriptionTimewindow.tsOffset;
this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs; this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs + this.subscriptionTimewindow.tsOffset;
} }
} }
@ -1107,12 +1116,13 @@ export class WidgetSubscription implements IWidgetSubscription {
private updateComparisonTimewindow() { private updateComparisonTimewindow() {
this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000;
this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone;
if (this.timewindowForComparison.realtimeWindowMs) { if (this.timewindowForComparison.realtimeWindowMs) {
this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf();
this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf(); this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf();
} else if (this.timewindowForComparison.fixedWindow) { } else if (this.timewindowForComparison.fixedWindow) {
this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs; this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset;
this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs; this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset;
} }
} }
@ -1339,7 +1349,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.onDataUpdated(); this.onDataUpdated();
} }
private alarmsUpdated(_updated: Array<AlarmData>, alarms: PageData<AlarmData>) { private alarmsUpdated(updated: Array<AlarmData>, alarms: PageData<AlarmData>) {
this.alarmsLoaded(alarms, 0, 0); this.alarmsLoaded(alarms, 0, 0);
} }

1
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -91,6 +91,7 @@
direction="left" direction="left"
tooltipPosition="below" tooltipPosition="below"
aggregation="true" aggregation="true"
timezone="true"
[(ngModel)]="dashboardCtx.dashboardTimewindow"> [(ngModel)]="dashboardCtx.dashboardTimewindow">
</tb-timewindow> </tb-timewindow>
<tb-filters-edit [fxShow]="!isEdit && displayFilters()" <tb-filters-edit [fxShow]="!isEdit && displayFilters()"

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

@ -95,6 +95,7 @@
<tb-timewindow *ngIf="widget.hasTimewindow" <tb-timewindow *ngIf="widget.hasTimewindow"
#timewindowComponent #timewindowComponent
aggregation="{{widget.hasAggregation}}" aggregation="{{widget.hasAggregation}}"
timezone="true"
[isEdit]="isEdit" [isEdit]="isEdit"
[(ngModel)]="widgetComponent.widget.config.timewindow" [(ngModel)]="widgetComponent.widget.config.timewindow"
(ngModelChange)="widgetComponent.onTimewindowChanged($event)"> (ngModelChange)="widgetComponent.onTimewindowChanged($event)">

12
ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts

@ -55,7 +55,13 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { AddEntityDialogComponent } from './add-entity-dialog.component'; import { AddEntityDialogComponent } from './add-entity-dialog.component';
import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models';
import { HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; import {
calculateIntervalEndTime,
calculateIntervalStartTime,
getCurrentTime,
HistoryWindowType,
Timewindow
} from '@shared/models/time/time.models';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
import { isDefined, isUndefined } from '@core/utils'; import { isDefined, isUndefined } from '@core/utils';
@ -296,6 +302,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
const currentTime = Date.now(); const currentTime = Date.now();
timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs;
timePageLink.endTime = currentTime; timePageLink.endTime = currentTime;
} else if (this.timewindow.history.historyType === HistoryWindowType.INTERVAL) {
const currentDate = getCurrentTime();
timePageLink.startTime = calculateIntervalStartTime(this.timewindow.history.quickInterval, currentDate);
timePageLink.endTime = calculateIntervalEndTime(this.timewindow.history.quickInterval, currentDate);
} else { } else {
timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs; timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs;
timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs; timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs;

9
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts

@ -94,11 +94,10 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems) items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems)
}); });
this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => {
getDefaultTimezone().subscribe((defaultTimezone) => { const defaultTimezone = getDefaultTimezone();
this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false}); this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false});
this.updateValidators(type, true); this.updateValidators(type, true);
this.alarmScheduleForm.updateValueAndValidity(); this.alarmScheduleForm.updateValueAndValidity();
});
}); });
this.alarmScheduleForm.valueChanges.subscribe(() => { this.alarmScheduleForm.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();

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

@ -49,7 +49,7 @@
formControlName="quickInterval" formControlName="quickInterval"
onlyCurrentInterval="true" onlyCurrentInterval="true"
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" [fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
[required]="timewindow.selectedTab === timewindowTypes.HISTORY && [required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval> style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval>
</section> </section>
@ -99,7 +99,7 @@
formControlName="quickInterval" formControlName="quickInterval"
[fxShow]="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" [fxShow]="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
[required]="timewindow.selectedTab === timewindowTypes.HISTORY && [required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval> style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval>
</section> </section>
</mat-radio-button> </mat-radio-button>
@ -177,6 +177,16 @@
predefinedName="aggregation.group-interval"> predefinedName="aggregation.group-interval">
</tb-timeinterval> </tb-timeinterval>
</div> </div>
<div *ngIf="timezone" class="mat-content mat-padding" fxLayout="row">
<section fxLayout="column" [fxShow]="isEdit">
<label class="tb-small hide-label" translate>timewindow.hide</label>
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideTimezone"
(ngModelChange)="onHideTimezoneChanged()"></mat-checkbox>
</section>
<tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone"
formControlName="timezone">
</tb-timezone-select>
</div>
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
<button type="button" <button type="button"
mat-button mat-button

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

@ -38,6 +38,7 @@ export interface TimewindowPanelData {
historyOnly: boolean; historyOnly: boolean;
timewindow: Timewindow; timewindow: Timewindow;
aggregation: boolean; aggregation: boolean;
timezone: boolean;
isEdit: boolean; isEdit: boolean;
} }
@ -52,6 +53,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
aggregation = false; aggregation = false;
timezone = false;
isEdit = false; isEdit = false;
timewindow: Timewindow; timewindow: Timewindow;
@ -82,6 +85,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
this.historyOnly = data.historyOnly; this.historyOnly = data.historyOnly;
this.timewindow = data.timewindow; this.timewindow = data.timewindow;
this.aggregation = data.aggregation; this.aggregation = data.aggregation;
this.timezone = data.timezone;
this.isEdit = data.isEdit; this.isEdit = data.isEdit;
} }
@ -89,6 +93,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
const hideInterval = this.timewindow.hideInterval || false; const hideInterval = this.timewindow.hideInterval || 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;
this.timewindowForm = this.fb.group({ this.timewindowForm = this.fb.group({
realtime: this.fb.group( realtime: this.fb.group(
@ -154,7 +159,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
disabled: hideAggInterval disabled: hideAggInterval
}, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())]) }, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())])
} }
) ),
timezone: this.fb.control({
value: this.timewindow.timezone !== 'undefined'
? this.timewindow.timezone : null,
disabled: hideTimezone
})
}); });
} }
@ -171,7 +181,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
timewindowMs: timewindowFormValue.history.timewindowMs, timewindowMs: timewindowFormValue.history.timewindowMs,
interval: timewindowFormValue.history.interval, interval: timewindowFormValue.history.interval,
fixedTimewindow: timewindowFormValue.history.fixedTimewindow, fixedTimewindow: timewindowFormValue.history.fixedTimewindow,
quickInterval: timewindowFormValue.history.quickInterval quickInterval: timewindowFormValue.history.quickInterval,
}; };
if (this.aggregation) { if (this.aggregation) {
this.timewindow.aggregation = { this.timewindow.aggregation = {
@ -179,6 +189,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
limit: timewindowFormValue.aggregation.limit limit: timewindowFormValue.aggregation.limit
}; };
} }
if (this.timezone) {
this.timewindow.timezone = timewindowFormValue.timezone;
}
this.result = this.timewindow; this.result = this.timewindow;
this.overlayRef.dispose(); this.overlayRef.dispose();
} }
@ -276,4 +289,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
this.timewindowForm.markAsDirty(); this.timewindowForm.markAsDirty();
} }
onHideTimezoneChanged() {
if (this.timewindow.hideTimezone) {
this.timewindowForm.get('timezone').disable({emitEvent: false});
} else {
this.timewindowForm.get('timezone').enable({emitEvent: false});
}
this.timewindowForm.markAsDirty();
}
} }

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

@ -91,6 +91,17 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
return this.aggregationValue; return this.aggregationValue;
} }
timezoneValue = false;
@Input()
set timezone(val) {
this.timezoneValue = coerceBooleanProperty(val);
}
get timezone() {
return this.timezoneValue;
}
isToolbarValue = false; isToolbarValue = false;
@Input() @Input()
@ -171,7 +182,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
}); });
if (isGtXs) { if (isGtXs) {
config.minWidth = '417px'; config.minWidth = '417px';
config.maxHeight = '440px'; config.maxHeight = '500px';
const panelHeight = 375; const panelHeight = 375;
const panelWidth = 417; const panelWidth = 417;
const el = this.timewindowPanelOrigin.elementRef.nativeElement; const el = this.timewindowPanelOrigin.elementRef.nativeElement;
@ -227,6 +238,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
timewindow: deepClone(this.innerValue), timewindow: deepClone(this.innerValue),
historyOnly: this.historyOnly, historyOnly: this.historyOnly,
aggregation: this.aggregation, aggregation: this.aggregation,
timezone: this.timezone,
isEdit: this.isEdit isEdit: this.isEdit
} }
); );

57
ui-ngx/src/app/shared/components/time/timezone-select.component.ts

@ -16,7 +16,7 @@
import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable, of } from 'rxjs';
import { map, mergeMap, share, tap } from 'rxjs/operators'; import { map, mergeMap, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
@ -43,10 +43,6 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
defaultTimezoneId: string = null; defaultTimezoneId: string = null;
timezones$ = getTimezones().pipe(
share()
);
@Input() @Input()
set defaultTimezone(timezone: string) { set defaultTimezone(timezone: string) {
if (this.defaultTimezoneId !== timezone) { if (this.defaultTimezoneId !== timezone) {
@ -138,23 +134,20 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
writeValue(value: string | null): void { writeValue(value: string | null): void {
this.searchText = ''; this.searchText = '';
getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe( const foundTimezone = getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue);
(foundTimezone) => { if (foundTimezone !== null) {
if (foundTimezone !== null) { this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false});
this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false}); if (foundTimezone.id !== value) {
if (foundTimezone.id !== value) { setTimeout(() => {
setTimeout(() => { this.updateView(foundTimezone.id);
this.updateView(foundTimezone.id); }, 0);
}, 0); } else {
} else { this.modelValue = value;
this.modelValue = value;
}
} else {
this.modelValue = null;
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
}
} }
); } else {
this.modelValue = null;
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
}
this.dirty = true; this.dirty = true;
} }
@ -170,14 +163,12 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
this.ignoreClosePanel = false; this.ignoreClosePanel = false;
} else { } else {
if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) { if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) {
getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe( const defaultTimezoneInfo = getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue);
(defaultTimezoneInfo) => { if (defaultTimezoneInfo !== null) {
if (defaultTimezoneInfo !== null) { this.ngZone.run(() => {
this.ngZone.run(() => { this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true});
this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true}); });
}); }
}
});
} }
} }
} }
@ -196,12 +187,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> {
this.searchText = searchText; this.searchText = searchText;
if (searchText && searchText.length) { if (searchText && searchText.length) {
return getTimezones().pipe( return of(getTimezones().filter((timezoneInfo) =>
map((timezones) => timezones.filter((timezoneInfo) => timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())));
timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())))
);
} }
return getTimezones(); return of(getTimezones());
} }
clear() { clear() {

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

@ -430,6 +430,38 @@ export class EntityDataUpdate extends DataUpdate<EntityData> {
constructor(msg: EntityDataUpdateMsg) { constructor(msg: EntityDataUpdateMsg) {
super(msg); super(msg);
} }
public prepareData(tsOffset: number) {
if (this.data) {
this.processEntityData(this.data.data, tsOffset);
}
if (this.update) {
this.processEntityData(this.update, tsOffset);
}
}
private processEntityData(data: Array<EntityData>, tsOffset: number) {
for (const entityData of data) {
if (entityData.timeseries) {
for (const key of Object.keys(entityData.timeseries)) {
const tsValues = entityData.timeseries[key];
for (const tsValue of tsValues) {
tsValue.ts += tsOffset;
}
}
}
if (entityData.latest) {
for (const entityKeyType of Object.keys(entityData.latest)) {
const keyTypeValues = entityData.latest[entityKeyType];
for (const key of Object.keys(keyTypeValues)) {
const tsValue = keyTypeValues[key];
tsValue.ts += tsOffset;
}
}
}
}
}
} }
export class AlarmDataUpdate extends DataUpdate<AlarmData> { export class AlarmDataUpdate extends DataUpdate<AlarmData> {
@ -468,6 +500,8 @@ export class TelemetrySubscriber {
private zone: NgZone; private zone: NgZone;
private tsOffset = 0;
public subscriptionCommands: Array<WebsocketCmd>; public subscriptionCommands: Array<WebsocketCmd>;
public data$ = this.dataSubject.asObservable(); public data$ = this.dataSubject.asObservable();
@ -522,6 +556,10 @@ export class TelemetrySubscriber {
this.reconnectSubject.complete(); this.reconnectSubject.complete();
} }
public setTsOffset(tsOffset: number) {
this.tsOffset = tsOffset;
}
public onData(message: SubscriptionUpdate) { public onData(message: SubscriptionUpdate) {
const cmdId = message.subscriptionId; const cmdId = message.subscriptionId;
let keys: string[]; let keys: string[];
@ -545,6 +583,9 @@ export class TelemetrySubscriber {
} }
public onEntityData(message: EntityDataUpdate) { public onEntityData(message: EntityDataUpdate) {
if (this.tsOffset) {
message.prepareData(this.tsOffset);
}
if (this.zone) { if (this.zone) {
this.zone.run( this.zone.run(
() => { () => {

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

@ -15,11 +15,9 @@
/// ///
import { TimeService } from '@core/services/time.service'; import { TimeService } from '@core/services/time.service';
import { deepClone, isDefined, isDefinedAndNotNull, isUndefined } from '@app/core/utils'; import { deepClone, isDefined, isUndefined } from '@app/core/utils';
import * as moment_ from 'moment'; import * as moment_ from 'moment';
import { Observable } from 'rxjs/internal/Observable'; import * as monentTz from 'moment-timezone';
import { from, of } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
const moment = moment_; const moment = moment_;
@ -97,10 +95,12 @@ export interface Timewindow {
hideInterval?: boolean; hideInterval?: boolean;
hideAggregation?: boolean; hideAggregation?: boolean;
hideAggInterval?: boolean; hideAggInterval?: boolean;
hideTimezone?: boolean;
selectedTab?: TimewindowType; selectedTab?: TimewindowType;
realtime?: RealtimeWindow; realtime?: RealtimeWindow;
history?: HistoryWindow; history?: HistoryWindow;
aggregation?: Aggregation; aggregation?: Aggregation;
timezone?: string;
} }
export interface SubscriptionAggregation extends Aggregation { export interface SubscriptionAggregation extends Aggregation {
@ -112,6 +112,8 @@ export interface SubscriptionAggregation extends Aggregation {
export interface SubscriptionTimewindow { export interface SubscriptionTimewindow {
startTs?: number; startTs?: number;
quickInterval?: QuickTimeInterval; quickInterval?: QuickTimeInterval;
timezone?: string;
tsOffset?: number;
realtimeWindowMs?: number; realtimeWindowMs?: number;
fixedWindow?: FixedWindow; fixedWindow?: FixedWindow;
aggregation?: SubscriptionAggregation; aggregation?: SubscriptionAggregation;
@ -121,6 +123,7 @@ export interface WidgetTimewindow {
minTime?: number; minTime?: number;
maxTime?: number; maxTime?: number;
interval?: number; interval?: number;
timezone?: string;
stDiff?: number; stDiff?: number;
} }
@ -178,6 +181,7 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
hideInterval: false, hideInterval: false,
hideAggregation: false, hideAggregation: false,
hideAggInterval: false, hideAggInterval: false,
hideTimezone: false,
selectedTab: TimewindowType.REALTIME, selectedTab: TimewindowType.REALTIME,
realtime: { realtime: {
realtimeType: RealtimeWindowType.LAST_INTERVAL, realtimeType: RealtimeWindowType.LAST_INTERVAL,
@ -209,6 +213,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
model.hideInterval = value.hideInterval; model.hideInterval = value.hideInterval;
model.hideAggregation = value.hideAggregation; model.hideAggregation = value.hideAggregation;
model.hideAggInterval = value.hideAggInterval; model.hideAggInterval = value.hideAggInterval;
model.hideTimezone = value.hideTimezone;
if (isUndefined(value.selectedTab)) { if (isUndefined(value.selectedTab)) {
if (value.realtime) { if (value.realtime) {
model.selectedTab = TimewindowType.REALTIME; model.selectedTab = TimewindowType.REALTIME;
@ -266,6 +271,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
} }
model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2);
} }
model.timezone = value.timezone;
} }
return model; return model;
} }
@ -292,6 +298,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
hideInterval: timewindow.hideInterval || false, hideInterval: timewindow.hideInterval || false,
hideAggregation: timewindow.hideAggregation || false, hideAggregation: timewindow.hideAggregation || false,
hideAggInterval: timewindow.hideAggInterval || false, hideAggInterval: timewindow.hideAggInterval || false,
hideTimezone: timewindow.hideTimezone || false,
selectedTab: TimewindowType.HISTORY, selectedTab: TimewindowType.HISTORY,
history: { history: {
historyType: HistoryWindowType.FIXED, historyType: HistoryWindowType.FIXED,
@ -304,7 +311,8 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
aggregation: { aggregation: {
type: aggType, type: aggType,
limit limit
} },
timezone: timewindow.timezone
}; };
return historyTimewindow; return historyTimewindow;
} }
@ -318,8 +326,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
interval: SECOND, interval: SECOND,
limit: timeService.getMaxDatapointsLimit(), limit: timeService.getMaxDatapointsLimit(),
type: AggregationType.AVG type: AggregationType.AVG
} },
timezone: timewindow.timezone,
tsOffset: 0
}; };
if (timewindow.timezone) {
const tz = getTimezone(timewindow.timezone);
const localOffset = moment().utcOffset();
subscriptionTimewindow.tsOffset = (tz.utcOffset() - localOffset) * 60 * 1000;
}
let aggTimewindow = 0; let aggTimewindow = 0;
if (stateData) { if (stateData) {
subscriptionTimewindow.aggregation.type = AggregationType.NONE; subscriptionTimewindow.aggregation.type = AggregationType.NONE;
@ -345,20 +360,25 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
} }
} }
if (realtimeType === RealtimeWindowType.INTERVAL) { if (realtimeType === RealtimeWindowType.INTERVAL) {
subscriptionTimewindow.realtimeWindowMs = getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval); const currentDate = getCurrentTime(timewindow.timezone);
subscriptionTimewindow.realtimeWindowMs =
getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, currentDate);
subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval; subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval;
subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate);
} else { } else {
subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs;
subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs;
} }
subscriptionTimewindow.aggregation.interval = subscriptionTimewindow.aggregation.interval =
timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval,
subscriptionTimewindow.aggregation.type); subscriptionTimewindow.aggregation.type);
subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs;
const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval;
aggTimewindow = subscriptionTimewindow.realtimeWindowMs; aggTimewindow = subscriptionTimewindow.realtimeWindowMs;
if (startDiff && realtimeType !== RealtimeWindowType.INTERVAL) { if (realtimeType !== RealtimeWindowType.INTERVAL) {
subscriptionTimewindow.startTs -= startDiff; const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval;
aggTimewindow += subscriptionTimewindow.aggregation.interval; if (startDiff) {
subscriptionTimewindow.startTs -= startDiff;
aggTimewindow += subscriptionTimewindow.aggregation.interval;
}
} }
} else { } else {
let historyType = timewindow.history.historyType; let historyType = timewindow.history.historyType;
@ -372,23 +392,24 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
} }
} }
if (historyType === HistoryWindowType.LAST_INTERVAL) { if (historyType === HistoryWindowType.LAST_INTERVAL) {
const currentTime = Date.now(); const currentDate = getCurrentTime(timewindow.timezone);
const currentTime = currentDate.valueOf();
subscriptionTimewindow.fixedWindow = { subscriptionTimewindow.fixedWindow = {
startTimeMs: currentTime - timewindow.history.timewindowMs, startTimeMs: currentTime - timewindow.history.timewindowMs,
endTimeMs: currentTime endTimeMs: currentTime
}; };
aggTimewindow = timewindow.history.timewindowMs; aggTimewindow = timewindow.history.timewindowMs;
} else if (historyType === HistoryWindowType.INTERVAL) { } else if (historyType === HistoryWindowType.INTERVAL) {
const currentDate = moment(); const currentDate = getCurrentTime(timewindow.timezone);
subscriptionTimewindow.fixedWindow = { subscriptionTimewindow.fixedWindow = {
startTimeMs: calculateIntervalStartTime(timewindow.history.quickInterval, null, currentDate), startTimeMs: calculateIntervalStartTime(timewindow.history.quickInterval, currentDate),
endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, null, currentDate) endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate)
}; };
aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
} else { } else {
subscriptionTimewindow.fixedWindow = { subscriptionTimewindow.fixedWindow = {
startTimeMs: timewindow.history.fixedTimewindow.startTimeMs, startTimeMs: timewindow.history.fixedTimewindow.startTimeMs - subscriptionTimewindow.tsOffset,
endTimeMs: timewindow.history.fixedTimewindow.endTimeMs endTimeMs: timewindow.history.fixedTimewindow.endTimeMs - subscriptionTimewindow.tsOffset
}; };
aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
} }
@ -404,88 +425,85 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
return subscriptionTimewindow; return subscriptionTimewindow;
} }
function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval): number { function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval, currentDate: moment_.Moment): number {
const currentDate = moment();
switch (interval) { switch (interval) {
case QuickTimeInterval.CURRENT_HOUR: case QuickTimeInterval.CURRENT_HOUR:
return currentDate.diff(currentDate.clone().startOf('hour')) return HOUR;
case QuickTimeInterval.CURRENT_DAY: case QuickTimeInterval.CURRENT_DAY:
case QuickTimeInterval.CURRENT_DAY_SO_FAR: case QuickTimeInterval.CURRENT_DAY_SO_FAR:
return currentDate.diff(currentDate.clone().startOf('day')); return DAY;
case QuickTimeInterval.CURRENT_WEEK: case QuickTimeInterval.CURRENT_WEEK:
case QuickTimeInterval.CURRENT_WEEK_SO_FAR: case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
return currentDate.diff(currentDate.clone().startOf('week')); return WEEK;
case QuickTimeInterval.CURRENT_MONTH: case QuickTimeInterval.CURRENT_MONTH:
case QuickTimeInterval.CURRENT_MONTH_SO_FAR: case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
return currentDate.diff(currentDate.clone().startOf('month')); return currentDate.endOf('month').diff(currentDate.clone().startOf('month'));
case QuickTimeInterval.CURRENT_YEAR: case QuickTimeInterval.CURRENT_YEAR:
case QuickTimeInterval.CURRENT_YEAR_SO_FAR: case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
return currentDate.diff(currentDate.clone().startOf('year')); return currentDate.endOf('year').diff(currentDate.clone().startOf('year'));
} }
} }
export function calculateIntervalEndTime(interval: QuickTimeInterval, endTs = 0, nowDate?: moment_.Moment): number { export function calculateIntervalEndTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {
const currentDate = isDefinedAndNotNull(nowDate) ? nowDate.clone() : moment(); currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz);
switch (interval) { switch (interval) {
case QuickTimeInterval.YESTERDAY: case QuickTimeInterval.YESTERDAY:
currentDate.subtract(1, 'days'); currentDate.subtract(1, 'days');
return currentDate.endOf('day').valueOf(); return currentDate.endOf('day').valueOf();
case QuickTimeInterval.DAY_BEFORE_YESTERDAY: case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
currentDate.subtract(2, 'days'); currentDate.subtract(2, 'days');
return currentDate.endOf('day').valueOf(); return currentDate.endOf('day').valueOf();
case QuickTimeInterval.THIS_DAY_LAST_WEEK: case QuickTimeInterval.THIS_DAY_LAST_WEEK:
currentDate.subtract(1, 'weeks'); currentDate.subtract(1, 'weeks');
return currentDate.endOf('day').valueOf(); return currentDate.endOf('day').valueOf();
case QuickTimeInterval.PREVIOUS_WEEK: case QuickTimeInterval.PREVIOUS_WEEK:
currentDate.subtract(1, 'weeks'); currentDate.subtract(1, 'weeks');
return currentDate.endOf('week').valueOf(); return currentDate.endOf('week').valueOf();
case QuickTimeInterval.PREVIOUS_MONTH: case QuickTimeInterval.PREVIOUS_MONTH:
currentDate.subtract(1, 'months'); currentDate.subtract(1, 'months');
return currentDate.endOf('month').valueOf(); return currentDate.endOf('month').valueOf();
case QuickTimeInterval.PREVIOUS_YEAR: case QuickTimeInterval.PREVIOUS_YEAR:
currentDate.subtract(1, 'years'); currentDate.subtract(1, 'years');
return currentDate.endOf('year').valueOf(); return currentDate.endOf('year').valueOf();
case QuickTimeInterval.CURRENT_HOUR: case QuickTimeInterval.CURRENT_HOUR:
return currentDate.endOf('hour').valueOf(); return currentDate.endOf('hour').valueOf();
case QuickTimeInterval.CURRENT_DAY: case QuickTimeInterval.CURRENT_DAY:
return currentDate.endOf('day').valueOf(); return currentDate.endOf('day').valueOf();
case QuickTimeInterval.CURRENT_WEEK: case QuickTimeInterval.CURRENT_WEEK:
return currentDate.endOf('week').valueOf(); return currentDate.endOf('week').valueOf();
case QuickTimeInterval.CURRENT_MONTH: case QuickTimeInterval.CURRENT_MONTH:
return currentDate.endOf('month').valueOf(); return currentDate.endOf('month').valueOf();
case QuickTimeInterval.CURRENT_YEAR: case QuickTimeInterval.CURRENT_YEAR:
return currentDate.endOf('year').valueOf(); return currentDate.endOf('year').valueOf();
case QuickTimeInterval.CURRENT_DAY_SO_FAR: case QuickTimeInterval.CURRENT_DAY_SO_FAR:
case QuickTimeInterval.CURRENT_WEEK_SO_FAR: case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
case QuickTimeInterval.CURRENT_MONTH_SO_FAR: case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
case QuickTimeInterval.CURRENT_YEAR_SO_FAR: case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
return currentDate.valueOf(); return currentDate.valueOf();
default:
return endTs;
} }
} }
export function calculateIntervalStartTime(interval: QuickTimeInterval, startTS = 0, nowDate?: moment_.Moment): number { export function calculateIntervalStartTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {
const currentDate = isDefinedAndNotNull(nowDate) ? nowDate.clone() : moment(); currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz);
switch (interval) { switch (interval) {
case QuickTimeInterval.YESTERDAY: case QuickTimeInterval.YESTERDAY:
currentDate.subtract(1, 'days'); currentDate.subtract(1, 'days');
return currentDate.startOf('day').valueOf(); return currentDate.startOf('day').valueOf();
case QuickTimeInterval.DAY_BEFORE_YESTERDAY: case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
currentDate.subtract(2, 'days'); currentDate.subtract(2, 'days');
return currentDate.startOf('day').valueOf(); return currentDate.startOf('day').valueOf();
case QuickTimeInterval.THIS_DAY_LAST_WEEK: case QuickTimeInterval.THIS_DAY_LAST_WEEK:
currentDate.subtract(1, 'weeks'); currentDate.subtract(1, 'weeks');
return currentDate.startOf('day').valueOf(); return currentDate.startOf('day').valueOf();
case QuickTimeInterval.PREVIOUS_WEEK: case QuickTimeInterval.PREVIOUS_WEEK:
currentDate.subtract(1, 'weeks'); currentDate.subtract(1, 'weeks');
return currentDate.startOf('week').valueOf(); return currentDate.startOf('week').valueOf();
case QuickTimeInterval.PREVIOUS_MONTH: case QuickTimeInterval.PREVIOUS_MONTH:
currentDate.subtract(1, 'months'); currentDate.subtract(1, 'months');
return currentDate.startOf('month').valueOf(); return currentDate.startOf('month').valueOf();
case QuickTimeInterval.PREVIOUS_YEAR: case QuickTimeInterval.PREVIOUS_YEAR:
currentDate.subtract(1, 'years'); currentDate.subtract(1, 'years');
return currentDate.startOf('year').valueOf(); return currentDate.startOf('year').valueOf();
case QuickTimeInterval.CURRENT_HOUR: case QuickTimeInterval.CURRENT_HOUR:
return currentDate.startOf('hour').valueOf(); return currentDate.startOf('hour').valueOf();
case QuickTimeInterval.CURRENT_DAY: case QuickTimeInterval.CURRENT_DAY:
@ -500,8 +518,6 @@ export function calculateIntervalStartTime(interval: QuickTimeInterval, startTS
case QuickTimeInterval.CURRENT_YEAR: case QuickTimeInterval.CURRENT_YEAR:
case QuickTimeInterval.CURRENT_YEAR_SO_FAR: case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
return currentDate.startOf('year').valueOf(); return currentDate.startOf('year').valueOf();
default:
return startTS;
} }
} }
@ -560,6 +576,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
cloned.hideInterval = timewindow.hideInterval || false; cloned.hideInterval = timewindow.hideInterval || 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;
if (isDefined(timewindow.selectedTab)) { if (isDefined(timewindow.selectedTab)) {
cloned.selectedTab = timewindow.selectedTab; cloned.selectedTab = timewindow.selectedTab;
if (timewindow.selectedTab === TimewindowType.REALTIME) { if (timewindow.selectedTab === TimewindowType.REALTIME) {
@ -569,6 +586,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
} }
} }
cloned.aggregation = deepClone(timewindow.aggregation); cloned.aggregation = deepClone(timewindow.aggregation);
cloned.timezone = timewindow.timezone;
return cloned; return cloned;
} }
@ -718,60 +736,50 @@ export interface TimezoneInfo {
let timezones: TimezoneInfo[] = null; let timezones: TimezoneInfo[] = null;
let defaultTimezone: string = null; let defaultTimezone: string = null;
export function getTimezones(): Observable<TimezoneInfo[]> { export function getTimezones(): TimezoneInfo[] {
if (timezones) { if (!timezones) {
return of(timezones); timezones = monentTz.tz.names().map((zoneName) => {
} else { const tz = monentTz.tz(zoneName);
return from(import('moment-timezone')).pipe( return {
map((monentTz) => { id: zoneName,
return monentTz.tz.names().map((zoneName) => { name: zoneName.replace(/_/g, ' '),
const tz = monentTz.tz(zoneName); offset: `UTC${tz.format('Z')}`,
return { nOffset: tz.utcOffset()
id: zoneName, };
name: zoneName.replace(/_/g, ' '), });
offset: `UTC${tz.format('Z')}`, }
nOffset: tz.utcOffset() return timezones;
}; }
});
}), export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): TimezoneInfo {
tap((zones) => { const timezoneList = getTimezones();
timezones = zones; let foundTimezone = timezoneId ? timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId) : null;
}) if (!foundTimezone) {
); if (userTimezoneByDefault) {
const userTimezone = getDefaultTimezone();
foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone);
} else if (defaultTimezoneId) {
foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId);
}
} }
return foundTimezone;
} }
export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): Observable<TimezoneInfo> { export function getDefaultTimezone(): string {
return getTimezones().pipe( if (!defaultTimezone) {
mergeMap((timezoneList) => { defaultTimezone = monentTz.tz.guess();
let foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId); }
if (!foundTimezone) { return defaultTimezone;
if (userTimezoneByDefault) {
return getDefaultTimezone().pipe(
map((userTimezone) => {
return timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone);
})
);
} else if (defaultTimezoneId) {
foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId);
}
}
return of(foundTimezone);
})
);
} }
export function getDefaultTimezone(): Observable<string> { export function getCurrentTime(tz?: string): moment_.Moment {
if (defaultTimezone) { if (tz) {
return of(defaultTimezone); return moment().tz(tz);
} else { } else {
return from(import('moment-timezone')).pipe( return moment();
map((monentTz) => {
return monentTz.tz.guess();
}),
tap((zone) => {
defaultTimezone = zone;
})
);
} }
} }
export function getTimezone(tz: string): moment_.Moment {
return moment.tz(tz);
}

Loading…
Cancel
Save