Commit 934981432f9ea5110eacded1e0f3ea1154c1e859

Authored by Igor Kulikov
1 parent 7bce418c

Improve comparison timewindow

@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 16
17 import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; 17 import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models';
18 import { 18 import {
19 - AggregationType, 19 + AggregationType, calculateIntervalComparisonEndTime,
20 calculateIntervalEndTime, 20 calculateIntervalEndTime,
21 calculateIntervalStartTime, 21 calculateIntervalStartTime,
22 getCurrentTime, 22 getCurrentTime,
@@ -26,6 +26,7 @@ import { @@ -26,6 +26,7 @@ import {
26 import { UtilsService } from '@core/services/utils.service'; 26 import { UtilsService } from '@core/services/utils.service';
27 import { deepClone } from '@core/utils'; 27 import { deepClone } from '@core/utils';
28 import Timeout = NodeJS.Timeout; 28 import Timeout = NodeJS.Timeout;
  29 +import * as moment_ from 'moment';
29 30
30 export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void; 31 export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void;
31 32
@@ -87,7 +88,7 @@ export class DataAggregator { @@ -87,7 +88,7 @@ export class DataAggregator {
87 private intervalTimeoutHandle: Timeout; 88 private intervalTimeoutHandle: Timeout;
88 private intervalScheduledTime: number; 89 private intervalScheduledTime: number;
89 90
90 - private startTs = this.subsTw.startTs + this.subsTw.tsOffset; 91 + private startTs: number;
91 private endTs: number; 92 private endTs: number;
92 private elapsed: number; 93 private elapsed: number;
93 94
@@ -139,13 +140,7 @@ export class DataAggregator { @@ -139,13 +140,7 @@ export class DataAggregator {
139 } 140 }
140 this.subsTw = subsTw; 141 this.subsTw = subsTw;
141 this.intervalScheduledTime = this.utils.currentPerfTime(); 142 this.intervalScheduledTime = this.utils.currentPerfTime();
142 - this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;  
143 - if (this.subsTw.quickInterval) {  
144 - const currentDate = this.getCurrentTime();  
145 - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;  
146 - } else {  
147 - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;  
148 - } 143 + this.calculateStartEndTs();
149 this.elapsed = 0; 144 this.elapsed = 0;
150 this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); 145 this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
151 this.resetPending = true; 146 this.resetPending = true;
@@ -168,12 +163,7 @@ export class DataAggregator { @@ -168,12 +163,7 @@ export class DataAggregator {
168 if (!this.dataReceived) { 163 if (!this.dataReceived) {
169 this.elapsed = 0; 164 this.elapsed = 0;
170 this.dataReceived = true; 165 this.dataReceived = true;
171 - if (this.subsTw.quickInterval) {  
172 - const currentDate = this.getCurrentTime();  
173 - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;  
174 - } else {  
175 - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;  
176 - } 166 + this.calculateStartEndTs();
177 } 167 }
178 if (this.resetPending) { 168 if (this.resetPending) {
179 this.resetPending = false; 169 this.resetPending = false;
@@ -198,6 +188,21 @@ export class DataAggregator { @@ -198,6 +188,21 @@ export class DataAggregator {
198 } 188 }
199 } 189 }
200 190
  191 + private calculateStartEndTs() {
  192 + this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;
  193 + if (this.subsTw.quickInterval) {
  194 + if (this.subsTw.timeForComparison === 'previousInterval') {
  195 + const currentDate = getCurrentTime(this.subsTw.timezone);
  196 + this.endTs = calculateIntervalComparisonEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
  197 + } else {
  198 + const currentDate = this.getCurrentTime();
  199 + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
  200 + }
  201 + } else {
  202 + this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
  203 + }
  204 + }
  205 +
201 private onInterval(history?: boolean, detectChanges?: boolean) { 206 private onInterval(history?: boolean, detectChanges?: boolean) {
202 const now = this.utils.currentPerfTime(); 207 const now = this.utils.currentPerfTime();
203 this.elapsed += now - this.intervalScheduledTime; 208 this.elapsed += now - this.intervalScheduledTime;
@@ -362,7 +367,7 @@ export class DataAggregator { @@ -362,7 +367,7 @@ export class DataAggregator {
362 367
363 private getCurrentTime() { 368 private getCurrentTime() {
364 if (this.subsTw.timeForComparison) { 369 if (this.subsTw.timeForComparison) {
365 - return getCurrentTimeForComparison(this.subsTw.timeForComparison, this.subsTw.timezone); 370 + return getCurrentTimeForComparison(this.subsTw.timeForComparison as moment_.unitOfTime.DurationConstructor, this.subsTw.timezone);
366 } else { 371 } else {
367 return getCurrentTime(this.subsTw.timezone); 372 return getCurrentTime(this.subsTw.timezone);
368 } 373 }
@@ -419,8 +419,8 @@ export class EntityDataSubscription { @@ -419,8 +419,8 @@ export class EntityDataSubscription {
419 latestTsOffsetChanged = this.subscriber.setTsOffset(this.latestTsOffset); 419 latestTsOffsetChanged = this.subscriber.setTsOffset(this.latestTsOffset);
420 } 420 }
421 if (latestTsOffsetChanged) { 421 if (latestTsOffsetChanged) {
422 - if (this.listener.initialPageDataChanged) {  
423 - this.listener.initialPageDataChanged(this.pageData); 422 + if (this.listener.forceReInit) {
  423 + this.listener.forceReInit();
424 } 424 }
425 } else if (!this.subsCommand.isEmpty()) { 425 } else if (!this.subsCommand.isEmpty()) {
426 this.subscriber.subscriptionCommands = [this.subsCommand]; 426 this.subscriber.subscriptionCommands = [this.subsCommand];
@@ -428,8 +428,8 @@ export class EntityDataSubscription { @@ -428,8 +428,8 @@ export class EntityDataSubscription {
428 } 428 }
429 } else if (this.datasourceType === DatasourceType.entityCount) { 429 } else if (this.datasourceType === DatasourceType.entityCount) {
430 if (this.subscriber.setTsOffset(this.latestTsOffset)) { 430 if (this.subscriber.setTsOffset(this.latestTsOffset)) {
431 - if (this.listener.initialPageDataChanged) {  
432 - this.listener.initialPageDataChanged(this.pageData); 431 + if (this.listener.forceReInit) {
  432 + this.listener.forceReInit();
433 } 433 }
434 } 434 }
435 } else if (this.datasourceType === DatasourceType.function) { 435 } else if (this.datasourceType === DatasourceType.function) {
@@ -40,6 +40,7 @@ export interface EntityDataListener { @@ -40,6 +40,7 @@ export interface EntityDataListener {
40 datasourceIndex: number, pageLink: EntityDataPageLink) => void; 40 datasourceIndex: number, pageLink: EntityDataPageLink) => void;
41 dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; 41 dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void;
42 initialPageDataChanged?: (nextPageData: PageData<EntityData>) => void; 42 initialPageDataChanged?: (nextPageData: PageData<EntityData>) => void;
  43 + forceReInit?: () => void;
43 updateRealtimeSubscription?: () => SubscriptionTimewindow; 44 updateRealtimeSubscription?: () => SubscriptionTimewindow;
44 setRealtimeSubscription?: (subscriptionTimewindow: SubscriptionTimewindow) => void; 45 setRealtimeSubscription?: (subscriptionTimewindow: SubscriptionTimewindow) => void;
45 subscriptionOptions?: EntityDataSubscriptionOptions; 46 subscriptionOptions?: EntityDataSubscriptionOptions;
@@ -209,6 +209,7 @@ export interface WidgetSubscriptionCallbacks { @@ -209,6 +209,7 @@ export interface WidgetSubscriptionCallbacks {
209 onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void; 209 onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void;
210 onSubscriptionMessage?: (subscription: IWidgetSubscription, message: SubscriptionMessage) => void; 210 onSubscriptionMessage?: (subscription: IWidgetSubscription, message: SubscriptionMessage) => void;
211 onInitialPageDataChanged?: (subscription: IWidgetSubscription, nextPageData: PageData<EntityData>) => void; 211 onInitialPageDataChanged?: (subscription: IWidgetSubscription, nextPageData: PageData<EntityData>) => void;
  212 + forceReInit?: () => void;
212 dataLoading?: (subscription: IWidgetSubscription) => void; 213 dataLoading?: (subscription: IWidgetSubscription) => void;
213 legendDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void; 214 legendDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void;
214 timeWindowUpdated?: (subscription: IWidgetSubscription, timeWindowConfig: Timewindow) => void; 215 timeWindowUpdated?: (subscription: IWidgetSubscription, timeWindowConfig: Timewindow) => void;
@@ -269,6 +270,7 @@ export interface IWidgetSubscription { @@ -269,6 +270,7 @@ export interface IWidgetSubscription {
269 hiddenData?: Array<{data: DataSet}>; 270 hiddenData?: Array<{data: DataSet}>;
270 timeWindowConfig?: Timewindow; 271 timeWindowConfig?: Timewindow;
271 timeWindow?: WidgetTimewindow; 272 timeWindow?: WidgetTimewindow;
  273 + comparisonEnabled?: boolean;
272 comparisonTimeWindow?: WidgetTimewindow; 274 comparisonTimeWindow?: WidgetTimewindow;
273 275
274 alarms?: PageData<AlarmData>; 276 alarms?: PageData<AlarmData>;
@@ -36,6 +36,18 @@ import { @@ -36,6 +36,18 @@ import {
36 widgetType 36 widgetType
37 } from '@app/shared/models/widget.models'; 37 } from '@app/shared/models/widget.models';
38 import { HttpErrorResponse } from '@angular/common/http'; 38 import { HttpErrorResponse } from '@angular/common/http';
  39 +import {
  40 + calculateIntervalEndTime,
  41 + calculateIntervalStartTime,
  42 + calculateTsOffset, ComparisonDuration,
  43 + createSubscriptionTimewindow,
  44 + createTimewindowForComparison,
  45 + getCurrentTime, isHistoryTypeTimewindow,
  46 + SubscriptionTimewindow,
  47 + Timewindow, timewindowTypeChanged,
  48 + toHistoryTimewindow,
  49 + WidgetTimewindow
  50 +} from '@app/shared/models/time/time.models';
39 import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; 51 import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
40 import { CancelAnimationFrame } from '@core/services/raf.service'; 52 import { CancelAnimationFrame } from '@core/services/raf.service';
41 import { EntityType } from '@shared/models/entity-type.models'; 53 import { EntityType } from '@shared/models/entity-type.models';
@@ -56,19 +68,6 @@ import { @@ -56,19 +68,6 @@ import {
56 } from '@shared/models/query/query.models'; 68 } from '@shared/models/query/query.models';
57 import { map } from 'rxjs/operators'; 69 import { map } from 'rxjs/operators';
58 import { AlarmDataListener } from '@core/api/alarm-data.service'; 70 import { AlarmDataListener } from '@core/api/alarm-data.service';
59 -import {  
60 - calculateIntervalEndTime,  
61 - calculateIntervalStartTime,  
62 - calculateTsOffset,  
63 - createSubscriptionTimewindow,  
64 - createTimewindowForComparison,  
65 - getCurrentTime,  
66 - isHistoryTypeTimewindow,  
67 - SubscriptionTimewindow,  
68 - Timewindow,  
69 - toHistoryTimewindow,  
70 - WidgetTimewindow  
71 -} from '@app/shared/models/time/time.models';  
72 71
73 const moment = moment_; 72 const moment = moment_;
74 73
@@ -108,7 +107,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -108,7 +107,7 @@ export class WidgetSubscription implements IWidgetSubscription {
108 decimals: number; 107 decimals: number;
109 units: string; 108 units: string;
110 comparisonEnabled: boolean; 109 comparisonEnabled: boolean;
111 - timeForComparison: moment_.unitOfTime.DurationConstructor; 110 + timeForComparison: ComparisonDuration;
112 comparisonTimeWindow: WidgetTimewindow; 111 comparisonTimeWindow: WidgetTimewindow;
113 timewindowForComparison: SubscriptionTimewindow; 112 timewindowForComparison: SubscriptionTimewindow;
114 113
@@ -199,6 +198,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -199,6 +198,7 @@ export class WidgetSubscription implements IWidgetSubscription {
199 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {}); 198 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {});
200 this.callbacks.onSubscriptionMessage = this.callbacks.onSubscriptionMessage || (() => {}); 199 this.callbacks.onSubscriptionMessage = this.callbacks.onSubscriptionMessage || (() => {});
201 this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {}); 200 this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {});
  201 + this.callbacks.forceReInit = this.callbacks.forceReInit || (() => {});
202 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {}); 202 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {});
203 this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {}); 203 this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {});
204 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); 204 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {});
@@ -229,7 +229,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -229,7 +229,7 @@ export class WidgetSubscription implements IWidgetSubscription {
229 } 229 }
230 230
231 this.subscriptionTimewindow = null; 231 this.subscriptionTimewindow = null;
232 - this.comparisonEnabled = options.comparisonEnabled; 232 + this.comparisonEnabled = options.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig);
233 if (this.comparisonEnabled) { 233 if (this.comparisonEnabled) {
234 this.timeForComparison = options.timeForComparison; 234 this.timeForComparison = options.timeForComparison;
235 235
@@ -388,7 +388,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -388,7 +388,7 @@ export class WidgetSubscription implements IWidgetSubscription {
388 this.notifyDataLoaded(); 388 this.notifyDataLoaded();
389 return of(null); 389 return of(null);
390 } 390 }
391 - if (this.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig)) { 391 + if (this.comparisonEnabled) {
392 const additionalDatasources: Datasource[] = []; 392 const additionalDatasources: Datasource[] = [];
393 this.configuredDatasources.forEach((datasource, datasourceIndex) => { 393 this.configuredDatasources.forEach((datasource, datasourceIndex) => {
394 const additionalDataKeys: DataKey[] = []; 394 const additionalDataKeys: DataKey[] = [];
@@ -419,20 +419,13 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -419,20 +419,13 @@ export class WidgetSubscription implements IWidgetSubscription {
419 this.dataLoaded(pageData, data1, datasourceIndex, pageLink, true); 419 this.dataLoaded(pageData, data1, datasourceIndex, pageLink, true);
420 }, 420 },
421 initialPageDataChanged: this.initialPageDataChanged.bind(this), 421 initialPageDataChanged: this.initialPageDataChanged.bind(this),
  422 + forceReInit: this.forceReInit.bind(this),
422 dataUpdated: this.dataUpdated.bind(this), 423 dataUpdated: this.dataUpdated.bind(this),
423 updateRealtimeSubscription: () => { 424 updateRealtimeSubscription: () => {
424 - if (this.comparisonEnabled && datasource.isAdditional && isHistoryTypeTimewindow(this.timeWindowConfig)) {  
425 - return this.updateSubscriptionForComparison();  
426 - } else {  
427 - return this.updateRealtimeSubscription();  
428 - } 425 + return this.updateRealtimeSubscription();
429 }, 426 },
430 setRealtimeSubscription: (subscriptionTimewindow) => { 427 setRealtimeSubscription: (subscriptionTimewindow) => {
431 - if (this.comparisonEnabled && datasource.isAdditional && isHistoryTypeTimewindow(this.timeWindowConfig)) {  
432 - this.updateSubscriptionForComparison(subscriptionTimewindow);  
433 - } else {  
434 - this.updateRealtimeSubscription(deepClone(subscriptionTimewindow));  
435 - } 428 + this.updateRealtimeSubscription(deepClone(subscriptionTimewindow));
436 } 429 }
437 }; 430 };
438 this.entityDataListeners.push(listener); 431 this.entityDataListeners.push(listener);
@@ -585,8 +578,9 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -585,8 +578,9 @@ export class WidgetSubscription implements IWidgetSubscription {
585 if (this.type === widgetType.timeseries || this.type === widgetType.alarm) { 578 if (this.type === widgetType.timeseries || this.type === widgetType.alarm) {
586 if (this.useDashboardTimewindow) { 579 if (this.useDashboardTimewindow) {
587 if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { 580 if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
  581 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newDashboardTimewindow);
588 this.timeWindowConfig = deepClone(newDashboardTimewindow); 582 this.timeWindowConfig = deepClone(newDashboardTimewindow);
589 - this.update(); 583 + this.update(isTimewindowTypeChanged);
590 } 584 }
591 } 585 }
592 } else if (this.type === widgetType.latest) { 586 } else if (this.type === widgetType.latest) {
@@ -615,8 +609,9 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -615,8 +609,9 @@ export class WidgetSubscription implements IWidgetSubscription {
615 609
616 updateTimewindowConfig(newTimewindow: Timewindow): void { 610 updateTimewindowConfig(newTimewindow: Timewindow): void {
617 if (!this.useDashboardTimewindow) { 611 if (!this.useDashboardTimewindow) {
  612 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newTimewindow);
618 this.timeWindowConfig = newTimewindow; 613 this.timeWindowConfig = newTimewindow;
619 - this.update(); 614 + this.update(isTimewindowTypeChanged);
620 } 615 }
621 } 616 }
622 617
@@ -625,10 +620,11 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -625,10 +620,11 @@ export class WidgetSubscription implements IWidgetSubscription {
625 this.ctx.dashboardTimewindowApi.onResetTimewindow(); 620 this.ctx.dashboardTimewindowApi.onResetTimewindow();
626 } else { 621 } else {
627 if (this.originalTimewindow) { 622 if (this.originalTimewindow) {
  623 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow);
628 this.timeWindowConfig = deepClone(this.originalTimewindow); 624 this.timeWindowConfig = deepClone(this.originalTimewindow);
629 this.originalTimewindow = null; 625 this.originalTimewindow = null;
630 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); 626 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
631 - this.update(); 627 + this.update(isTimewindowTypeChanged);
632 } 628 }
633 } 629 }
634 } 630 }
@@ -642,7 +638,8 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -642,7 +638,8 @@ export class WidgetSubscription implements IWidgetSubscription {
642 } 638 }
643 this.timeWindowConfig = toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs, interval, this.ctx.timeService); 639 this.timeWindowConfig = toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs, interval, this.ctx.timeService);
644 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); 640 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
645 - this.update(); 641 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow);
  642 + this.update(isTimewindowTypeChanged);
646 } 643 }
647 } 644 }
648 645
@@ -771,16 +768,20 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -771,16 +768,20 @@ export class WidgetSubscription implements IWidgetSubscription {
771 } 768 }
772 } 769 }
773 770
774 - update() { 771 + update(isTimewindowTypeChanged = false) {
775 if (this.type !== widgetType.rpc) { 772 if (this.type !== widgetType.rpc) {
776 if (this.type === widgetType.alarm) { 773 if (this.type === widgetType.alarm) {
777 this.updateAlarmDataSubscription(); 774 this.updateAlarmDataSubscription();
778 } else { 775 } else {
779 - if (this.hasDataPageLink) {  
780 - this.updateDataSubscriptions(); 776 + if (this.type === widgetType.timeseries && this.options.comparisonEnabled && isTimewindowTypeChanged) {
  777 + this.forceReInit();
781 } else { 778 } else {
782 - this.notifyDataLoading();  
783 - this.dataSubscribe(); 779 + if (this.hasDataPageLink) {
  780 + this.updateDataSubscriptions();
  781 + } else {
  782 + this.notifyDataLoading();
  783 + this.dataSubscribe();
  784 + }
784 } 785 }
785 } 786 }
786 } 787 }
@@ -889,7 +890,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -889,7 +890,7 @@ export class WidgetSubscription implements IWidgetSubscription {
889 if (!this.hasDataPageLink) { 890 if (!this.hasDataPageLink) {
890 if (this.type === widgetType.timeseries && this.timeWindowConfig) { 891 if (this.type === widgetType.timeseries && this.timeWindowConfig) {
891 this.updateRealtimeSubscription(); 892 this.updateRealtimeSubscription();
892 - if (this.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig)) { 893 + if (this.comparisonEnabled) {
893 this.updateSubscriptionForComparison(); 894 this.updateSubscriptionForComparison();
894 } 895 }
895 } 896 }
@@ -905,7 +906,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -905,7 +906,7 @@ export class WidgetSubscription implements IWidgetSubscription {
905 const forceUpdate = !this.datasources.length; 906 const forceUpdate = !this.datasources.length;
906 const notifyDataLoaded = !this.entityDataListeners.filter((listener) => listener.subscription ? true : false).length; 907 const notifyDataLoaded = !this.entityDataListeners.filter((listener) => listener.subscription ? true : false).length;
907 this.entityDataListeners.forEach((listener) => { 908 this.entityDataListeners.forEach((listener) => {
908 - if (this.comparisonEnabled && listener.configDatasource.isAdditional && isHistoryTypeTimewindow(this.timeWindowConfig)) { 909 + if (this.comparisonEnabled && listener.configDatasource.isAdditional) {
909 listener.subscriptionTimewindow = this.timewindowForComparison; 910 listener.subscriptionTimewindow = this.timewindowForComparison;
910 } else { 911 } else {
911 listener.subscriptionTimewindow = this.subscriptionTimewindow; 912 listener.subscriptionTimewindow = this.subscriptionTimewindow;
@@ -1144,24 +1145,14 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1144,24 +1145,14 @@ export class WidgetSubscription implements IWidgetSubscription {
1144 private updateComparisonTimewindow() { 1145 private updateComparisonTimewindow() {
1145 this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; 1146 this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000;
1146 this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone; 1147 this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone;
1147 - if (this.timewindowForComparison.realtimeWindowMs) {  
1148 - this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf();  
1149 - this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf();  
1150 - } else if (this.timewindowForComparison.fixedWindow) {  
1151 - this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs;  
1152 - this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs; 1148 + if (this.timewindowForComparison.fixedWindow) {
  1149 + this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset;
  1150 + this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset;
1153 } 1151 }
1154 } 1152 }
1155 1153
1156 - private updateSubscriptionForComparison(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow {  
1157 - if (subscriptionTimewindow) {  
1158 - this.timewindowForComparison = subscriptionTimewindow;  
1159 - } else {  
1160 - if (!this.subscriptionTimewindow) {  
1161 - this.subscriptionTimewindow = this.updateRealtimeSubscription();  
1162 - }  
1163 - this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);  
1164 - } 1154 + private updateSubscriptionForComparison(): SubscriptionTimewindow {
  1155 + this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);
1165 this.updateComparisonTimewindow(); 1156 this.updateComparisonTimewindow();
1166 return this.timewindowForComparison; 1157 return this.timewindowForComparison;
1167 } 1158 }
@@ -1170,6 +1161,10 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1170,6 +1161,10 @@ export class WidgetSubscription implements IWidgetSubscription {
1170 this.callbacks.onInitialPageDataChanged(this, nextPageData); 1161 this.callbacks.onInitialPageDataChanged(this, nextPageData);
1171 } 1162 }
1172 1163
  1164 + private forceReInit() {
  1165 + this.callbacks.forceReInit();
  1166 + }
  1167 +
1173 private dataLoaded(pageData: PageData<EntityData>, 1168 private dataLoaded(pageData: PageData<EntityData>,
1174 data: Array<Array<DataSetHolder>>, 1169 data: Array<Array<DataSetHolder>>,
1175 datasourceIndex: number, pageLink: EntityDataPageLink, isUpdate: boolean) { 1170 datasourceIndex: number, pageLink: EntityDataPageLink, isUpdate: boolean) {
@@ -1262,7 +1257,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1262,7 +1257,7 @@ export class WidgetSubscription implements IWidgetSubscription {
1262 index++; 1257 index++;
1263 }); 1258 });
1264 }); 1259 });
1265 - if (this.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig)) { 1260 + if (this.comparisonEnabled) {
1266 this.datasourcePages.forEach(datasourcePage => { 1261 this.datasourcePages.forEach(datasourcePage => {
1267 datasourcePage.data.forEach((datasource, dIndex) => { 1262 datasourcePage.data.forEach((datasource, dIndex) => {
1268 if (datasource.isAdditional) { 1263 if (datasource.isAdditional) {
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 import { DataKey, Datasource, DatasourceData, JsonSettingsSchema } from '@shared/models/widget.models'; 20 import { DataKey, Datasource, DatasourceData, JsonSettingsSchema } from '@shared/models/widget.models';
21 import * as moment_ from 'moment'; 21 import * as moment_ from 'moment';
22 import { DataKeyType } from "@shared/models/telemetry/telemetry.models"; 22 import { DataKeyType } from "@shared/models/telemetry/telemetry.models";
  23 +import { ComparisonDuration } from '@shared/models/time/time.models';
23 24
24 export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph'; 25 export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph';
25 26
@@ -142,7 +143,7 @@ export interface TbFlotBaseSettings { @@ -142,7 +143,7 @@ export interface TbFlotBaseSettings {
142 143
143 export interface TbFlotComparisonSettings { 144 export interface TbFlotComparisonSettings {
144 comparisonEnabled: boolean; 145 comparisonEnabled: boolean;
145 - timeForComparison: moment_.unitOfTime.DurationConstructor; 146 + timeForComparison: ComparisonDuration;
146 xaxisSecond: TbFlotSecondXAxisSettings; 147 xaxisSecond: TbFlotSecondXAxisSettings;
147 } 148 }
148 149
@@ -543,7 +544,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = { @@ -543,7 +544,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = {
543 timeForComparison: { 544 timeForComparison: {
544 title: 'Time to show historical data', 545 title: 'Time to show historical data',
545 type: 'string', 546 type: 'string',
546 - default: 'months' 547 + default: 'previousInterval'
547 }, 548 },
548 xaxisSecond: { 549 xaxisSecond: {
549 title: 'Second X axis', 550 title: 'Second X axis',
@@ -577,6 +578,10 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = { @@ -577,6 +578,10 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = {
577 multiple: false, 578 multiple: false,
578 items: [ 579 items: [
579 { 580 {
  581 + value: 'previousInterval',
  582 + label: 'Previous interval (default)'
  583 + },
  584 + {
580 value: 'days', 585 value: 'days',
581 label: 'Day ago' 586 label: 'Day ago'
582 }, 587 },
@@ -586,7 +591,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = { @@ -586,7 +591,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = {
586 }, 591 },
587 { 592 {
588 value: 'months', 593 value: 'months',
589 - label: 'Month ago (default)' 594 + label: 'Month ago'
590 }, 595 },
591 { 596 {
592 value: 'years', 597 value: 'years',
@@ -74,6 +74,7 @@ export class TbFlot { @@ -74,6 +74,7 @@ export class TbFlot {
74 private readonly utils: UtilsService; 74 private readonly utils: UtilsService;
75 75
76 private settings: TbFlotSettings; 76 private settings: TbFlotSettings;
  77 + private comparisonEnabled: boolean;
77 78
78 private readonly tooltip: JQuery<any>; 79 private readonly tooltip: JQuery<any>;
79 80
@@ -263,29 +264,7 @@ export class TbFlot { @@ -263,29 +264,7 @@ export class TbFlot {
263 }; 264 };
264 } 265 }
265 266
266 - if (this.settings.comparisonEnabled) {  
267 - const xaxis = deepClone(this.xaxis);  
268 - xaxis.position = 'top';  
269 - if (this.settings.xaxisSecond) {  
270 - if (this.settings.xaxisSecond.showLabels === false) {  
271 - xaxis.tickFormatter = () => {  
272 - return '';  
273 - };  
274 - }  
275 - xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null;  
276 - xaxis.position = this.settings.xaxisSecond.axisPosition;  
277 - }  
278 - xaxis.tickLength = 0;  
279 - this.options.xaxes.push(xaxis);  
280 -  
281 - this.options.series = {  
282 - stack: false  
283 - };  
284 - } else {  
285 - this.options.series = {  
286 - stack: this.settings.stack === true  
287 - };  
288 - } 267 + this.options.series = {};
289 268
290 this.options.crosshair = { 269 this.options.crosshair = {
291 mode: 'x' 270 mode: 'x'
@@ -364,7 +343,6 @@ export class TbFlot { @@ -364,7 +343,6 @@ export class TbFlot {
364 343
365 // Experimental 344 // Experimental
366 this.animatedPie = this.settings.animatedPie === true; 345 this.animatedPie = this.settings.animatedPie === true;
367 -  
368 } 346 }
369 347
370 if (this.ctx.defaultSubscription) { 348 if (this.ctx.defaultSubscription) {
@@ -372,10 +350,29 @@ export class TbFlot { @@ -372,10 +350,29 @@ export class TbFlot {
372 } 350 }
373 } 351 }
374 352
375 -  
376 private init($element: JQuery<any>, subscription: IWidgetSubscription) { 353 private init($element: JQuery<any>, subscription: IWidgetSubscription) {
377 - this.subscription = subscription;  
378 this.$element = $element; 354 this.$element = $element;
  355 + this.subscription = subscription;
  356 + this.comparisonEnabled = this.subscription ? this.subscription.comparisonEnabled : this.settings.comparisonEnabled;
  357 + if (this.comparisonEnabled) {
  358 + const xaxis = deepClone(this.xaxis);
  359 + xaxis.position = 'top';
  360 + if (this.settings.xaxisSecond) {
  361 + if (this.settings.xaxisSecond.showLabels === false) {
  362 + xaxis.tickFormatter = () => {
  363 + return '';
  364 + };
  365 + }
  366 + xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null;
  367 + xaxis.position = this.settings.xaxisSecond.axisPosition;
  368 + }
  369 + xaxis.tickLength = 0;
  370 + this.options.xaxes.push(xaxis);
  371 +
  372 + this.options.series.stack = false;
  373 + } else {
  374 + this.options.series.stack = this.settings.stack === true;
  375 + }
379 const colors: string[] = []; 376 const colors: string[] = [];
380 this.yaxes = []; 377 this.yaxes = [];
381 const yaxesMap: {[units: string]: TbFlotAxisOptions} = {}; 378 const yaxesMap: {[units: string]: TbFlotAxisOptions} = {};
@@ -387,7 +384,7 @@ export class TbFlot { @@ -387,7 +384,7 @@ export class TbFlot {
387 this.settings.dataKeysListForLabels.forEach((item) => { 384 this.settings.dataKeysListForLabels.forEach((item) => {
388 item.settings = {}; 385 item.settings = {};
389 }); 386 });
390 - subscription.datasources.forEach((item) => { 387 + this.subscription.datasources.forEach((item) => {
391 const datasource: Datasource = { 388 const datasource: Datasource = {
392 type: item.type, 389 type: item.type,
393 entityType: item.entityType, 390 entityType: item.entityType,
@@ -425,7 +422,7 @@ export class TbFlot { @@ -425,7 +422,7 @@ export class TbFlot {
425 fill: keySettings.fillLines === true 422 fill: keySettings.fillLines === true
426 }; 423 };
427 424
428 - if (this.settings.stack && !this.settings.comparisonEnabled) { 425 + if (this.settings.stack && !this.comparisonEnabled) {
429 series.stack = !keySettings.excludeFromStacking; 426 series.stack = !keySettings.excludeFromStacking;
430 } else { 427 } else {
431 series.stack = false; 428 series.stack = false;
@@ -557,7 +554,7 @@ export class TbFlot { @@ -557,7 +554,7 @@ export class TbFlot {
557 } 554 }
558 this.options.xaxes[0].min = this.subscription.timeWindow.minTime; 555 this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
559 this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; 556 this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
560 - if (this.settings.comparisonEnabled) { 557 + if (this.comparisonEnabled) {
561 this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; 558 this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
562 this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; 559 this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
563 } 560 }
@@ -636,7 +633,7 @@ export class TbFlot { @@ -636,7 +633,7 @@ export class TbFlot {
636 633
637 this.options.xaxes[0].min = this.subscription.timeWindow.minTime; 634 this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
638 this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; 635 this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
639 - if (this.settings.comparisonEnabled) { 636 + if (this.comparisonEnabled) {
640 this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; 637 this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
641 this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; 638 this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
642 } 639 }
@@ -654,7 +651,7 @@ export class TbFlot { @@ -654,7 +651,7 @@ export class TbFlot {
654 } else { 651 } else {
655 this.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; 652 this.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime;
656 this.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime; 653 this.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime;
657 - if (this.settings.comparisonEnabled) { 654 + if (this.comparisonEnabled) {
658 this.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; 655 this.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
659 this.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; 656 this.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
660 } 657 }
@@ -1293,7 +1290,7 @@ export class TbFlot { @@ -1293,7 +1290,7 @@ export class TbFlot {
1293 const results: TbFlotHoverInfo[] = [{ 1290 const results: TbFlotHoverInfo[] = [{
1294 seriesHover: [] 1291 seriesHover: []
1295 }]; 1292 }];
1296 - if (this.settings.comparisonEnabled) { 1293 + if (this.comparisonEnabled) {
1297 results.push({ 1294 results.push({
1298 seriesHover: [] 1295 seriesHover: []
1299 }); 1296 });
@@ -857,6 +857,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -857,6 +857,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
857 onInitialPageDataChanged: (subscription, nextPageData) => { 857 onInitialPageDataChanged: (subscription, nextPageData) => {
858 this.reInit(); 858 this.reInit();
859 }, 859 },
  860 + forceReInit: () => {
  861 + this.reInit();
  862 + },
860 dataLoading: (subscription) => { 863 dataLoading: (subscription) => {
861 if (this.loadingData !== subscription.loadingData) { 864 if (this.loadingData !== subscription.loadingData) {
862 this.loadingData = subscription.loadingData; 865 this.loadingData = subscription.loadingData;
@@ -28,6 +28,8 @@ export const DAY = 24 * HOUR; @@ -28,6 +28,8 @@ export const DAY = 24 * HOUR;
28 export const WEEK = 7 * DAY; 28 export const WEEK = 7 * DAY;
29 export const YEAR = DAY * 365; 29 export const YEAR = DAY * 365;
30 30
  31 +export type ComparisonDuration = moment_.unitOfTime.DurationConstructor | 'previousInterval';
  32 +
31 export enum TimewindowType { 33 export enum TimewindowType {
32 REALTIME, 34 REALTIME,
33 HISTORY 35 HISTORY
@@ -118,7 +120,7 @@ export interface SubscriptionTimewindow { @@ -118,7 +120,7 @@ export interface SubscriptionTimewindow {
118 realtimeWindowMs?: number; 120 realtimeWindowMs?: number;
119 fixedWindow?: FixedWindow; 121 fixedWindow?: FixedWindow;
120 aggregation?: SubscriptionAggregation; 122 aggregation?: SubscriptionAggregation;
121 - timeForComparison?: moment_.unitOfTime.DurationConstructor; 123 + timeForComparison?: ComparisonDuration;
122 } 124 }
123 125
124 export interface WidgetTimewindow { 126 export interface WidgetTimewindow {
@@ -319,6 +321,15 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, @@ -319,6 +321,15 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
319 return historyTimewindow; 321 return historyTimewindow;
320 } 322 }
321 323
  324 +export function timewindowTypeChanged(newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean {
  325 + if (!newTimewindow || !oldTimewindow) {
  326 + return false;
  327 + }
  328 + const newType = getTimewindowType(newTimewindow);
  329 + const oldType = getTimewindowType(oldTimewindow);
  330 + return newType !== oldType;
  331 +}
  332 +
322 export function calculateTsOffset(timezone?: string): number { 333 export function calculateTsOffset(timezone?: string): number {
323 if (timezone) { 334 if (timezone) {
324 const tz = getTimezone(timezone); 335 const tz = getTimezone(timezone);
@@ -555,8 +566,78 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { @@ -555,8 +566,78 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number {
555 } 566 }
556 } 567 }
557 568
  569 +export function calculateIntervalComparisonStartTime(interval: QuickTimeInterval,
  570 + currentDate: moment_.Moment): number {
  571 + switch (interval) {
  572 + case QuickTimeInterval.YESTERDAY:
  573 + case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
  574 + case QuickTimeInterval.CURRENT_DAY:
  575 + case QuickTimeInterval.CURRENT_DAY_SO_FAR:
  576 + currentDate.subtract(1, 'days');
  577 + return currentDate.startOf('day').valueOf();
  578 + case QuickTimeInterval.THIS_DAY_LAST_WEEK:
  579 + currentDate.subtract(1, 'weeks');
  580 + return currentDate.startOf('day').valueOf();
  581 + case QuickTimeInterval.PREVIOUS_WEEK:
  582 + case QuickTimeInterval.CURRENT_WEEK:
  583 + case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  584 + currentDate.subtract(1, 'weeks');
  585 + return currentDate.startOf('week').valueOf();
  586 + case QuickTimeInterval.PREVIOUS_MONTH:
  587 + case QuickTimeInterval.CURRENT_MONTH:
  588 + case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
  589 + currentDate.subtract(1, 'months');
  590 + return currentDate.startOf('month').valueOf();
  591 + case QuickTimeInterval.PREVIOUS_YEAR:
  592 + case QuickTimeInterval.CURRENT_YEAR:
  593 + case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
  594 + currentDate.subtract(1, 'years');
  595 + return currentDate.startOf('year').valueOf();
  596 + case QuickTimeInterval.CURRENT_HOUR:
  597 + currentDate.subtract(1, 'hour');
  598 + return currentDate.startOf('hour').valueOf();
  599 + }
  600 +}
  601 +
  602 +export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval,
  603 + currentDate: moment_.Moment): number {
  604 + switch (interval) {
  605 + case QuickTimeInterval.YESTERDAY:
  606 + case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
  607 + case QuickTimeInterval.CURRENT_DAY:
  608 + currentDate.subtract(1, 'days');
  609 + return currentDate.endOf('day').valueOf();
  610 + case QuickTimeInterval.CURRENT_DAY_SO_FAR:
  611 + return currentDate.subtract(1, 'days').valueOf();
  612 + case QuickTimeInterval.THIS_DAY_LAST_WEEK:
  613 + currentDate.subtract(1, 'weeks');
  614 + return currentDate.endOf('day').valueOf();
  615 + case QuickTimeInterval.PREVIOUS_WEEK:
  616 + case QuickTimeInterval.CURRENT_WEEK:
  617 + currentDate.subtract(1, 'weeks');
  618 + return currentDate.endOf('week').valueOf();
  619 + case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  620 + return currentDate.subtract(1, 'week').valueOf();
  621 + case QuickTimeInterval.PREVIOUS_MONTH:
  622 + case QuickTimeInterval.CURRENT_MONTH:
  623 + currentDate.subtract(1, 'months');
  624 + return currentDate.endOf('month').valueOf();
  625 + case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
  626 + return currentDate.subtract(1, 'month').valueOf();
  627 + case QuickTimeInterval.PREVIOUS_YEAR:
  628 + case QuickTimeInterval.CURRENT_YEAR:
  629 + currentDate.subtract(1, 'years');
  630 + return currentDate.endOf('year').valueOf();
  631 + case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
  632 + return currentDate.subtract(1, 'year').valueOf();
  633 + case QuickTimeInterval.CURRENT_HOUR:
  634 + currentDate.subtract(1, 'hour');
  635 + return currentDate.endOf('hour').valueOf();
  636 + }
  637 +}
  638 +
558 export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow, 639 export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow,
559 - timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow { 640 + timeUnit: ComparisonDuration): SubscriptionTimewindow {
560 const timewindowForComparison: SubscriptionTimewindow = { 641 const timewindowForComparison: SubscriptionTimewindow = {
561 fixedWindow: null, 642 fixedWindow: null,
562 realtimeWindowMs: null, 643 realtimeWindowMs: null,
@@ -564,18 +645,30 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti @@ -564,18 +645,30 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti
564 tsOffset: subscriptionTimewindow.tsOffset 645 tsOffset: subscriptionTimewindow.tsOffset
565 }; 646 };
566 647
567 - if (subscriptionTimewindow.realtimeWindowMs) {  
568 - if (subscriptionTimewindow.quickInterval) {  
569 - timewindowForComparison.quickInterval = subscriptionTimewindow.quickInterval;  
570 - timewindowForComparison.timeForComparison = timeUnit; 648 + if (subscriptionTimewindow.fixedWindow) {
  649 + let startTimeMs;
  650 + let endTimeMs;
  651 + if (timeUnit === 'previousInterval') {
  652 + if (subscriptionTimewindow.quickInterval) {
  653 + const startDate = moment(subscriptionTimewindow.fixedWindow.startTimeMs);
  654 + const endDate = moment(subscriptionTimewindow.fixedWindow.endTimeMs);
  655 + if (subscriptionTimewindow.timezone) {
  656 + startDate.tz(subscriptionTimewindow.timezone);
  657 + endDate.tz(subscriptionTimewindow.timezone);
  658 + }
  659 + startTimeMs = calculateIntervalComparisonStartTime(subscriptionTimewindow.quickInterval, startDate);
  660 + endTimeMs = calculateIntervalComparisonEndTime(subscriptionTimewindow.quickInterval, endDate);
  661 + } else {
  662 + const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
  663 + endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs;
  664 + startTimeMs = endTimeMs - timeInterval;
  665 + }
  666 + } else {
  667 + const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
  668 + endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf();
  669 + startTimeMs = endTimeMs - timeInterval;
571 } 670 }
572 - timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf();  
573 - timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs;  
574 - } else if (subscriptionTimewindow.fixedWindow) {  
575 - const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;  
576 - const endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf();  
577 -  
578 - timewindowForComparison.startTs = endTimeMs - timeInterval; 671 + timewindowForComparison.startTs = startTimeMs;
579 timewindowForComparison.fixedWindow = { 672 timewindowForComparison.fixedWindow = {
580 startTimeMs: timewindowForComparison.startTs, 673 startTimeMs: timewindowForComparison.startTs,
581 endTimeMs 674 endTimeMs
@@ -1798,6 +1798,7 @@ @@ -1798,6 +1798,7 @@
1798 "avg": "avg", 1798 "avg": "avg",
1799 "total": "total", 1799 "total": "total",
1800 "comparison-time-ago": { 1800 "comparison-time-ago": {
  1801 + "previousInterval": "(previous interval)",
1801 "days": "(day ago)", 1802 "days": "(day ago)",
1802 "weeks": "(week ago)", 1803 "weeks": "(week ago)",
1803 "months": "(month ago)", 1804 "months": "(month ago)",