Commit b5444e6c2792ac2e483d4bc2d151679c61c65d5b

Authored by Igor Kulikov
2 parents 0a27101e 93498143

Merge branch 'vvlladd28-improvement/comparison-flot'

@@ -16,14 +16,17 @@ @@ -16,14 +16,17 @@
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, getCurrentTime,  
22 - QuickTimeInterval, SubscriptionTimewindow 21 + calculateIntervalStartTime,
  22 + getCurrentTime,
  23 + getCurrentTimeForComparison,
  24 + SubscriptionTimewindow
23 } from '@shared/models/time/time.models'; 25 } from '@shared/models/time/time.models';
24 import { UtilsService } from '@core/services/utils.service'; 26 import { UtilsService } from '@core/services/utils.service';
25 import { deepClone } from '@core/utils'; 27 import { deepClone } from '@core/utils';
26 import Timeout = NodeJS.Timeout; 28 import Timeout = NodeJS.Timeout;
  29 +import * as moment_ from 'moment';
27 30
28 export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void; 31 export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void;
29 32
@@ -85,7 +88,7 @@ export class DataAggregator { @@ -85,7 +88,7 @@ export class DataAggregator {
85 private intervalTimeoutHandle: Timeout; 88 private intervalTimeoutHandle: Timeout;
86 private intervalScheduledTime: number; 89 private intervalScheduledTime: number;
87 90
88 - private startTs = this.subsTw.startTs + this.subsTw.tsOffset; 91 + private startTs: number;
89 private endTs: number; 92 private endTs: number;
90 private elapsed: number; 93 private elapsed: number;
91 94
@@ -137,12 +140,7 @@ export class DataAggregator { @@ -137,12 +140,7 @@ export class DataAggregator {
137 } 140 }
138 this.subsTw = subsTw; 141 this.subsTw = subsTw;
139 this.intervalScheduledTime = this.utils.currentPerfTime(); 142 this.intervalScheduledTime = this.utils.currentPerfTime();
140 - this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;  
141 - if (this.subsTw.quickInterval) {  
142 - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;  
143 - } else {  
144 - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;  
145 - } 143 + this.calculateStartEndTs();
146 this.elapsed = 0; 144 this.elapsed = 0;
147 this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); 145 this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
148 this.resetPending = true; 146 this.resetPending = true;
@@ -165,11 +163,7 @@ export class DataAggregator { @@ -165,11 +163,7 @@ export class DataAggregator {
165 if (!this.dataReceived) { 163 if (!this.dataReceived) {
166 this.elapsed = 0; 164 this.elapsed = 0;
167 this.dataReceived = true; 165 this.dataReceived = true;
168 - if (this.subsTw.quickInterval) {  
169 - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;  
170 - } else {  
171 - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;  
172 - } 166 + this.calculateStartEndTs();
173 } 167 }
174 if (this.resetPending) { 168 if (this.resetPending) {
175 this.resetPending = false; 169 this.resetPending = false;
@@ -194,6 +188,21 @@ export class DataAggregator { @@ -194,6 +188,21 @@ export class DataAggregator {
194 } 188 }
195 } 189 }
196 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 +
197 private onInterval(history?: boolean, detectChanges?: boolean) { 206 private onInterval(history?: boolean, detectChanges?: boolean) {
198 const now = this.utils.currentPerfTime(); 207 const now = this.utils.currentPerfTime();
199 this.elapsed += now - this.intervalScheduledTime; 208 this.elapsed += now - this.intervalScheduledTime;
@@ -207,7 +216,7 @@ export class DataAggregator { @@ -207,7 +216,7 @@ export class DataAggregator {
207 if (delta || !this.data) { 216 if (delta || !this.data) {
208 const tickTs = delta * this.subsTw.aggregation.interval; 217 const tickTs = delta * this.subsTw.aggregation.interval;
209 if (this.subsTw.quickInterval) { 218 if (this.subsTw.quickInterval) {
210 - const currentDate = getCurrentTime(this.subsTw.timezone); 219 + const currentDate = this.getCurrentTime();
211 this.startTs = calculateIntervalStartTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; 220 this.startTs = calculateIntervalStartTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
212 this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; 221 this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
213 } else { 222 } else {
@@ -356,5 +365,13 @@ export class DataAggregator { @@ -356,5 +365,13 @@ export class DataAggregator {
356 } 365 }
357 } 366 }
358 367
  368 + private getCurrentTime() {
  369 + if (this.subsTw.timeForComparison) {
  370 + return getCurrentTimeForComparison(this.subsTw.timeForComparison as moment_.unitOfTime.DurationConstructor, this.subsTw.timezone);
  371 + } else {
  372 + return getCurrentTime(this.subsTw.timezone);
  373 + }
  374 + }
  375 +
359 } 376 }
360 377
@@ -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>;
@@ -39,12 +39,12 @@ import { HttpErrorResponse } from '@angular/common/http'; @@ -39,12 +39,12 @@ import { HttpErrorResponse } from '@angular/common/http';
39 import { 39 import {
40 calculateIntervalEndTime, 40 calculateIntervalEndTime,
41 calculateIntervalStartTime, 41 calculateIntervalStartTime,
42 - calculateTsOffset, 42 + calculateTsOffset, ComparisonDuration,
43 createSubscriptionTimewindow, 43 createSubscriptionTimewindow,
44 createTimewindowForComparison, 44 createTimewindowForComparison,
45 - getCurrentTime, 45 + getCurrentTime, isHistoryTypeTimewindow,
46 SubscriptionTimewindow, 46 SubscriptionTimewindow,
47 - Timewindow, 47 + Timewindow, timewindowTypeChanged,
48 toHistoryTimewindow, 48 toHistoryTimewindow,
49 WidgetTimewindow 49 WidgetTimewindow
50 } from '@app/shared/models/time/time.models'; 50 } from '@app/shared/models/time/time.models';
@@ -107,7 +107,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -107,7 +107,7 @@ export class WidgetSubscription implements IWidgetSubscription {
107 decimals: number; 107 decimals: number;
108 units: string; 108 units: string;
109 comparisonEnabled: boolean; 109 comparisonEnabled: boolean;
110 - timeForComparison: moment_.unitOfTime.DurationConstructor; 110 + timeForComparison: ComparisonDuration;
111 comparisonTimeWindow: WidgetTimewindow; 111 comparisonTimeWindow: WidgetTimewindow;
112 timewindowForComparison: SubscriptionTimewindow; 112 timewindowForComparison: SubscriptionTimewindow;
113 113
@@ -198,6 +198,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -198,6 +198,7 @@ export class WidgetSubscription implements IWidgetSubscription {
198 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {}); 198 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {});
199 this.callbacks.onSubscriptionMessage = this.callbacks.onSubscriptionMessage || (() => {}); 199 this.callbacks.onSubscriptionMessage = this.callbacks.onSubscriptionMessage || (() => {});
200 this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {}); 200 this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {});
  201 + this.callbacks.forceReInit = this.callbacks.forceReInit || (() => {});
201 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {}); 202 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {});
202 this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {}); 203 this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {});
203 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); 204 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {});
@@ -228,7 +229,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -228,7 +229,7 @@ export class WidgetSubscription implements IWidgetSubscription {
228 } 229 }
229 230
230 this.subscriptionTimewindow = null; 231 this.subscriptionTimewindow = null;
231 - this.comparisonEnabled = options.comparisonEnabled; 232 + this.comparisonEnabled = options.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig);
232 if (this.comparisonEnabled) { 233 if (this.comparisonEnabled) {
233 this.timeForComparison = options.timeForComparison; 234 this.timeForComparison = options.timeForComparison;
234 235
@@ -418,20 +419,13 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -418,20 +419,13 @@ export class WidgetSubscription implements IWidgetSubscription {
418 this.dataLoaded(pageData, data1, datasourceIndex, pageLink, true); 419 this.dataLoaded(pageData, data1, datasourceIndex, pageLink, true);
419 }, 420 },
420 initialPageDataChanged: this.initialPageDataChanged.bind(this), 421 initialPageDataChanged: this.initialPageDataChanged.bind(this),
  422 + forceReInit: this.forceReInit.bind(this),
421 dataUpdated: this.dataUpdated.bind(this), 423 dataUpdated: this.dataUpdated.bind(this),
422 updateRealtimeSubscription: () => { 424 updateRealtimeSubscription: () => {
423 - if (this.comparisonEnabled && datasource.isAdditional) {  
424 - return this.updateSubscriptionForComparison();  
425 - } else {  
426 - return this.updateRealtimeSubscription();  
427 - } 425 + return this.updateRealtimeSubscription();
428 }, 426 },
429 setRealtimeSubscription: (subscriptionTimewindow) => { 427 setRealtimeSubscription: (subscriptionTimewindow) => {
430 - if (this.comparisonEnabled && datasource.isAdditional) {  
431 - this.updateSubscriptionForComparison(subscriptionTimewindow);  
432 - } else {  
433 - this.updateRealtimeSubscription(deepClone(subscriptionTimewindow));  
434 - } 428 + this.updateRealtimeSubscription(deepClone(subscriptionTimewindow));
435 } 429 }
436 }; 430 };
437 this.entityDataListeners.push(listener); 431 this.entityDataListeners.push(listener);
@@ -584,8 +578,9 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -584,8 +578,9 @@ export class WidgetSubscription implements IWidgetSubscription {
584 if (this.type === widgetType.timeseries || this.type === widgetType.alarm) { 578 if (this.type === widgetType.timeseries || this.type === widgetType.alarm) {
585 if (this.useDashboardTimewindow) { 579 if (this.useDashboardTimewindow) {
586 if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { 580 if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
  581 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newDashboardTimewindow);
587 this.timeWindowConfig = deepClone(newDashboardTimewindow); 582 this.timeWindowConfig = deepClone(newDashboardTimewindow);
588 - this.update(); 583 + this.update(isTimewindowTypeChanged);
589 } 584 }
590 } 585 }
591 } else if (this.type === widgetType.latest) { 586 } else if (this.type === widgetType.latest) {
@@ -614,8 +609,9 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -614,8 +609,9 @@ export class WidgetSubscription implements IWidgetSubscription {
614 609
615 updateTimewindowConfig(newTimewindow: Timewindow): void { 610 updateTimewindowConfig(newTimewindow: Timewindow): void {
616 if (!this.useDashboardTimewindow) { 611 if (!this.useDashboardTimewindow) {
  612 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newTimewindow);
617 this.timeWindowConfig = newTimewindow; 613 this.timeWindowConfig = newTimewindow;
618 - this.update(); 614 + this.update(isTimewindowTypeChanged);
619 } 615 }
620 } 616 }
621 617
@@ -624,10 +620,11 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -624,10 +620,11 @@ export class WidgetSubscription implements IWidgetSubscription {
624 this.ctx.dashboardTimewindowApi.onResetTimewindow(); 620 this.ctx.dashboardTimewindowApi.onResetTimewindow();
625 } else { 621 } else {
626 if (this.originalTimewindow) { 622 if (this.originalTimewindow) {
  623 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow);
627 this.timeWindowConfig = deepClone(this.originalTimewindow); 624 this.timeWindowConfig = deepClone(this.originalTimewindow);
628 this.originalTimewindow = null; 625 this.originalTimewindow = null;
629 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); 626 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
630 - this.update(); 627 + this.update(isTimewindowTypeChanged);
631 } 628 }
632 } 629 }
633 } 630 }
@@ -641,7 +638,8 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -641,7 +638,8 @@ export class WidgetSubscription implements IWidgetSubscription {
641 } 638 }
642 this.timeWindowConfig = toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs, interval, this.ctx.timeService); 639 this.timeWindowConfig = toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs, interval, this.ctx.timeService);
643 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); 640 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
644 - this.update(); 641 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow);
  642 + this.update(isTimewindowTypeChanged);
645 } 643 }
646 } 644 }
647 645
@@ -770,16 +768,20 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -770,16 +768,20 @@ export class WidgetSubscription implements IWidgetSubscription {
770 } 768 }
771 } 769 }
772 770
773 - update() { 771 + update(isTimewindowTypeChanged = false) {
774 if (this.type !== widgetType.rpc) { 772 if (this.type !== widgetType.rpc) {
775 if (this.type === widgetType.alarm) { 773 if (this.type === widgetType.alarm) {
776 this.updateAlarmDataSubscription(); 774 this.updateAlarmDataSubscription();
777 } else { 775 } else {
778 - if (this.hasDataPageLink) {  
779 - this.updateDataSubscriptions(); 776 + if (this.type === widgetType.timeseries && this.options.comparisonEnabled && isTimewindowTypeChanged) {
  777 + this.forceReInit();
780 } else { 778 } else {
781 - this.notifyDataLoading();  
782 - this.dataSubscribe(); 779 + if (this.hasDataPageLink) {
  780 + this.updateDataSubscriptions();
  781 + } else {
  782 + this.notifyDataLoading();
  783 + this.dataSubscribe();
  784 + }
783 } 785 }
784 } 786 }
785 } 787 }
@@ -1143,24 +1145,14 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1143,24 +1145,14 @@ export class WidgetSubscription implements IWidgetSubscription {
1143 private updateComparisonTimewindow() { 1145 private updateComparisonTimewindow() {
1144 this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; 1146 this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000;
1145 this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone; 1147 this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone;
1146 - if (this.timewindowForComparison.realtimeWindowMs) {  
1147 - this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf();  
1148 - this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf();  
1149 - } else if (this.timewindowForComparison.fixedWindow) { 1148 + if (this.timewindowForComparison.fixedWindow) {
1150 this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset; 1149 this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset;
1151 this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset; 1150 this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset;
1152 } 1151 }
1153 } 1152 }
1154 1153
1155 - private updateSubscriptionForComparison(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow {  
1156 - if (subscriptionTimewindow) {  
1157 - this.timewindowForComparison = subscriptionTimewindow;  
1158 - } else {  
1159 - if (!this.subscriptionTimewindow) {  
1160 - this.subscriptionTimewindow = this.updateRealtimeSubscription();  
1161 - }  
1162 - this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);  
1163 - } 1154 + private updateSubscriptionForComparison(): SubscriptionTimewindow {
  1155 + this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);
1164 this.updateComparisonTimewindow(); 1156 this.updateComparisonTimewindow();
1165 return this.timewindowForComparison; 1157 return this.timewindowForComparison;
1166 } 1158 }
@@ -1169,6 +1161,10 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1169,6 +1161,10 @@ export class WidgetSubscription implements IWidgetSubscription {
1169 this.callbacks.onInitialPageDataChanged(this, nextPageData); 1161 this.callbacks.onInitialPageDataChanged(this, nextPageData);
1170 } 1162 }
1171 1163
  1164 + private forceReInit() {
  1165 + this.callbacks.forceReInit();
  1166 + }
  1167 +
1172 private dataLoaded(pageData: PageData<EntityData>, 1168 private dataLoaded(pageData: PageData<EntityData>,
1173 data: Array<Array<DataSetHolder>>, 1169 data: Array<Array<DataSetHolder>>,
1174 datasourceIndex: number, pageLink: EntityDataPageLink, isUpdate: boolean) { 1170 datasourceIndex: number, pageLink: EntityDataPageLink, isUpdate: boolean) {
@@ -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,6 +120,7 @@ export interface SubscriptionTimewindow { @@ -118,6 +120,7 @@ export interface SubscriptionTimewindow {
118 realtimeWindowMs?: number; 120 realtimeWindowMs?: number;
119 fixedWindow?: FixedWindow; 121 fixedWindow?: FixedWindow;
120 aggregation?: SubscriptionAggregation; 122 aggregation?: SubscriptionAggregation;
  123 + timeForComparison?: ComparisonDuration;
121 } 124 }
122 125
123 export interface WidgetTimewindow { 126 export interface WidgetTimewindow {
@@ -208,6 +211,14 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { @@ -208,6 +211,14 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
208 return timewindow; 211 return timewindow;
209 } 212 }
210 213
  214 +function getTimewindowType(timewindow: Timewindow): TimewindowType {
  215 + if (isUndefined(timewindow.selectedTab)) {
  216 + return isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY;
  217 + } else {
  218 + return timewindow.selectedTab;
  219 + }
  220 +}
  221 +
211 export function initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow { 222 export function initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow {
212 const model = defaultTimewindow(timeService); 223 const model = defaultTimewindow(timeService);
213 if (value) { 224 if (value) {
@@ -215,15 +226,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T @@ -215,15 +226,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
215 model.hideAggregation = value.hideAggregation; 226 model.hideAggregation = value.hideAggregation;
216 model.hideAggInterval = value.hideAggInterval; 227 model.hideAggInterval = value.hideAggInterval;
217 model.hideTimezone = value.hideTimezone; 228 model.hideTimezone = value.hideTimezone;
218 - if (isUndefined(value.selectedTab)) {  
219 - if (value.realtime) {  
220 - model.selectedTab = TimewindowType.REALTIME;  
221 - } else {  
222 - model.selectedTab = TimewindowType.HISTORY;  
223 - }  
224 - } else {  
225 - model.selectedTab = value.selectedTab;  
226 - } 229 + model.selectedTab = getTimewindowType(value);
227 if (model.selectedTab === TimewindowType.REALTIME) { 230 if (model.selectedTab === TimewindowType.REALTIME) {
228 if (isDefined(value.realtime.interval)) { 231 if (isDefined(value.realtime.interval)) {
229 model.realtime.interval = value.realtime.interval; 232 model.realtime.interval = value.realtime.interval;
@@ -318,6 +321,15 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, @@ -318,6 +321,15 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
318 return historyTimewindow; 321 return historyTimewindow;
319 } 322 }
320 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 +
321 export function calculateTsOffset(timezone?: string): number { 333 export function calculateTsOffset(timezone?: string): number {
322 if (timezone) { 334 if (timezone) {
323 const tz = getTimezone(timezone); 335 const tz = getTimezone(timezone);
@@ -328,6 +340,10 @@ export function calculateTsOffset(timezone?: string): number { @@ -328,6 +340,10 @@ export function calculateTsOffset(timezone?: string): number {
328 } 340 }
329 } 341 }
330 342
  343 +export function isHistoryTypeTimewindow(timewindow: Timewindow): boolean {
  344 + return getTimewindowType(timewindow) === TimewindowType.HISTORY;
  345 +}
  346 +
331 export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean, 347 export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean,
332 timeService: TimeService): SubscriptionTimewindow { 348 timeService: TimeService): SubscriptionTimewindow {
333 const subscriptionTimewindow: SubscriptionTimewindow = { 349 const subscriptionTimewindow: SubscriptionTimewindow = {
@@ -352,10 +368,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num @@ -352,10 +368,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
352 limit: timewindow.aggregation.limit || timeService.getMaxDatapointsLimit() 368 limit: timewindow.aggregation.limit || timeService.getMaxDatapointsLimit()
353 }; 369 };
354 } 370 }
355 - let selectedTab = timewindow.selectedTab;  
356 - if (isUndefined(selectedTab)) {  
357 - selectedTab = isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY;  
358 - } 371 + const selectedTab = getTimewindowType(timewindow);
359 if (selectedTab === TimewindowType.REALTIME) { 372 if (selectedTab === TimewindowType.REALTIME) {
360 let realtimeType = timewindow.realtime.realtimeType; 373 let realtimeType = timewindow.realtime.realtimeType;
361 if (isUndefined(realtimeType)) { 374 if (isUndefined(realtimeType)) {
@@ -553,22 +566,109 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { @@ -553,22 +566,109 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number {
553 } 566 }
554 } 567 }
555 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 +
556 export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow, 639 export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow,
557 - timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow { 640 + timeUnit: ComparisonDuration): SubscriptionTimewindow {
558 const timewindowForComparison: SubscriptionTimewindow = { 641 const timewindowForComparison: SubscriptionTimewindow = {
559 fixedWindow: null, 642 fixedWindow: null,
560 realtimeWindowMs: null, 643 realtimeWindowMs: null,
561 - aggregation: subscriptionTimewindow.aggregation 644 + aggregation: subscriptionTimewindow.aggregation,
  645 + tsOffset: subscriptionTimewindow.tsOffset
562 }; 646 };
563 647
564 - if (subscriptionTimewindow.realtimeWindowMs) {  
565 - timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf();  
566 - timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs;  
567 - } else if (subscriptionTimewindow.fixedWindow) {  
568 - const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;  
569 - const endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf();  
570 -  
571 - timewindowForComparison.startTs = endTimeMs - timeInterval; 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;
  670 + }
  671 + timewindowForComparison.startTs = startTimeMs;
572 timewindowForComparison.fixedWindow = { 672 timewindowForComparison.fixedWindow = {
573 startTimeMs: timewindowForComparison.startTs, 673 startTimeMs: timewindowForComparison.startTs,
574 endTimeMs 674 endTimeMs
@@ -797,3 +897,7 @@ export function getCurrentTime(tz?: string): moment_.Moment { @@ -797,3 +897,7 @@ export function getCurrentTime(tz?: string): moment_.Moment {
797 export function getTimezone(tz: string): moment_.Moment { 897 export function getTimezone(tz: string): moment_.Moment {
798 return moment.tz(tz); 898 return moment.tz(tz);
799 } 899 }
  900 +
  901 +export function getCurrentTimeForComparison(timeForComparison: moment_.unitOfTime.DurationConstructor, tz?: string): moment_.Moment {
  902 + return getCurrentTime(tz).subtract(1, timeForComparison);
  903 +}
@@ -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)",