From 934981432f9ea5110eacded1e0f3ea1154c1e859 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 18 Mar 2021 19:45:45 +0200 Subject: [PATCH] Improve comparison timewindow --- ui-ngx/src/app/core/api/data-aggregator.ts | 37 +++--- .../app/core/api/entity-data-subscription.ts | 8 +- .../src/app/core/api/entity-data.service.ts | 1 + ui-ngx/src/app/core/api/widget-api.models.ts | 2 + .../src/app/core/api/widget-subscription.ts | 101 +++++++-------- .../widget/lib/flot-widget.models.ts | 11 +- .../home/components/widget/lib/flot-widget.ts | 61 +++++---- .../components/widget/widget.component.ts | 3 + .../src/app/shared/models/time/time.models.ts | 119 ++++++++++++++++-- .../assets/locale/locale.constant-en_US.json | 1 + 10 files changed, 223 insertions(+), 121 deletions(-) diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index 4a73c6dafd..1749d89e4e 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -16,7 +16,7 @@ import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; import { - AggregationType, + AggregationType, calculateIntervalComparisonEndTime, calculateIntervalEndTime, calculateIntervalStartTime, getCurrentTime, @@ -26,6 +26,7 @@ import { import { UtilsService } from '@core/services/utils.service'; import { deepClone } from '@core/utils'; import Timeout = NodeJS.Timeout; +import * as moment_ from 'moment'; export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void; @@ -87,7 +88,7 @@ export class DataAggregator { private intervalTimeoutHandle: Timeout; private intervalScheduledTime: number; - private startTs = this.subsTw.startTs + this.subsTw.tsOffset; + private startTs: number; private endTs: number; private elapsed: number; @@ -139,13 +140,7 @@ export class DataAggregator { } this.subsTw = subsTw; this.intervalScheduledTime = this.utils.currentPerfTime(); - this.startTs = this.subsTw.startTs + this.subsTw.tsOffset; - if (this.subsTw.quickInterval) { - const currentDate = this.getCurrentTime(); - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; - } else { - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow; - } + this.calculateStartEndTs(); this.elapsed = 0; this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); this.resetPending = true; @@ -168,12 +163,7 @@ export class DataAggregator { if (!this.dataReceived) { this.elapsed = 0; this.dataReceived = true; - if (this.subsTw.quickInterval) { - const currentDate = this.getCurrentTime(); - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; - } else { - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow; - } + this.calculateStartEndTs(); } if (this.resetPending) { this.resetPending = false; @@ -198,6 +188,21 @@ export class DataAggregator { } } + private calculateStartEndTs() { + this.startTs = this.subsTw.startTs + this.subsTw.tsOffset; + if (this.subsTw.quickInterval) { + if (this.subsTw.timeForComparison === 'previousInterval') { + const currentDate = getCurrentTime(this.subsTw.timezone); + this.endTs = calculateIntervalComparisonEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; + } else { + const currentDate = this.getCurrentTime(); + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; + } + } else { + this.endTs = this.startTs + this.subsTw.aggregation.timeWindow; + } + } + private onInterval(history?: boolean, detectChanges?: boolean) { const now = this.utils.currentPerfTime(); this.elapsed += now - this.intervalScheduledTime; @@ -362,7 +367,7 @@ export class DataAggregator { private getCurrentTime() { if (this.subsTw.timeForComparison) { - return getCurrentTimeForComparison(this.subsTw.timeForComparison, this.subsTw.timezone); + return getCurrentTimeForComparison(this.subsTw.timeForComparison as moment_.unitOfTime.DurationConstructor, this.subsTw.timezone); } else { return getCurrentTime(this.subsTw.timezone); } diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index 7a26a5373d..c6f8121919 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -419,8 +419,8 @@ export class EntityDataSubscription { latestTsOffsetChanged = this.subscriber.setTsOffset(this.latestTsOffset); } if (latestTsOffsetChanged) { - if (this.listener.initialPageDataChanged) { - this.listener.initialPageDataChanged(this.pageData); + if (this.listener.forceReInit) { + this.listener.forceReInit(); } } else if (!this.subsCommand.isEmpty()) { this.subscriber.subscriptionCommands = [this.subsCommand]; @@ -428,8 +428,8 @@ export class EntityDataSubscription { } } else if (this.datasourceType === DatasourceType.entityCount) { if (this.subscriber.setTsOffset(this.latestTsOffset)) { - if (this.listener.initialPageDataChanged) { - this.listener.initialPageDataChanged(this.pageData); + if (this.listener.forceReInit) { + this.listener.forceReInit(); } } } else if (this.datasourceType === DatasourceType.function) { diff --git a/ui-ngx/src/app/core/api/entity-data.service.ts b/ui-ngx/src/app/core/api/entity-data.service.ts index 7f45cee73f..1567334d0a 100644 --- a/ui-ngx/src/app/core/api/entity-data.service.ts +++ b/ui-ngx/src/app/core/api/entity-data.service.ts @@ -40,6 +40,7 @@ export interface EntityDataListener { datasourceIndex: number, pageLink: EntityDataPageLink) => void; dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; initialPageDataChanged?: (nextPageData: PageData) => void; + forceReInit?: () => void; updateRealtimeSubscription?: () => SubscriptionTimewindow; setRealtimeSubscription?: (subscriptionTimewindow: SubscriptionTimewindow) => void; subscriptionOptions?: EntityDataSubscriptionOptions; diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index eff95a6f2d..fc95490b0d 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -209,6 +209,7 @@ export interface WidgetSubscriptionCallbacks { onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void; onSubscriptionMessage?: (subscription: IWidgetSubscription, message: SubscriptionMessage) => void; onInitialPageDataChanged?: (subscription: IWidgetSubscription, nextPageData: PageData) => void; + forceReInit?: () => void; dataLoading?: (subscription: IWidgetSubscription) => void; legendDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void; timeWindowUpdated?: (subscription: IWidgetSubscription, timeWindowConfig: Timewindow) => void; @@ -269,6 +270,7 @@ export interface IWidgetSubscription { hiddenData?: Array<{data: DataSet}>; timeWindowConfig?: Timewindow; timeWindow?: WidgetTimewindow; + comparisonEnabled?: boolean; comparisonTimeWindow?: WidgetTimewindow; alarms?: PageData; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 11ab462bdc..6663735bd5 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -36,6 +36,18 @@ import { widgetType } from '@app/shared/models/widget.models'; import { HttpErrorResponse } from '@angular/common/http'; +import { + calculateIntervalEndTime, + calculateIntervalStartTime, + calculateTsOffset, ComparisonDuration, + createSubscriptionTimewindow, + createTimewindowForComparison, + getCurrentTime, isHistoryTypeTimewindow, + SubscriptionTimewindow, + Timewindow, timewindowTypeChanged, + toHistoryTimewindow, + WidgetTimewindow +} from '@app/shared/models/time/time.models'; import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; import { CancelAnimationFrame } from '@core/services/raf.service'; import { EntityType } from '@shared/models/entity-type.models'; @@ -56,19 +68,6 @@ import { } from '@shared/models/query/query.models'; import { map } from 'rxjs/operators'; import { AlarmDataListener } from '@core/api/alarm-data.service'; -import { - calculateIntervalEndTime, - calculateIntervalStartTime, - calculateTsOffset, - createSubscriptionTimewindow, - createTimewindowForComparison, - getCurrentTime, - isHistoryTypeTimewindow, - SubscriptionTimewindow, - Timewindow, - toHistoryTimewindow, - WidgetTimewindow -} from '@app/shared/models/time/time.models'; const moment = moment_; @@ -108,7 +107,7 @@ export class WidgetSubscription implements IWidgetSubscription { decimals: number; units: string; comparisonEnabled: boolean; - timeForComparison: moment_.unitOfTime.DurationConstructor; + timeForComparison: ComparisonDuration; comparisonTimeWindow: WidgetTimewindow; timewindowForComparison: SubscriptionTimewindow; @@ -199,6 +198,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {}); this.callbacks.onSubscriptionMessage = this.callbacks.onSubscriptionMessage || (() => {}); this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {}); + this.callbacks.forceReInit = this.callbacks.forceReInit || (() => {}); this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {}); this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {}); this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); @@ -229,7 +229,7 @@ export class WidgetSubscription implements IWidgetSubscription { } this.subscriptionTimewindow = null; - this.comparisonEnabled = options.comparisonEnabled; + this.comparisonEnabled = options.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig); if (this.comparisonEnabled) { this.timeForComparison = options.timeForComparison; @@ -388,7 +388,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.notifyDataLoaded(); return of(null); } - if (this.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig)) { + if (this.comparisonEnabled) { const additionalDatasources: Datasource[] = []; this.configuredDatasources.forEach((datasource, datasourceIndex) => { const additionalDataKeys: DataKey[] = []; @@ -419,20 +419,13 @@ export class WidgetSubscription implements IWidgetSubscription { this.dataLoaded(pageData, data1, datasourceIndex, pageLink, true); }, initialPageDataChanged: this.initialPageDataChanged.bind(this), + forceReInit: this.forceReInit.bind(this), dataUpdated: this.dataUpdated.bind(this), updateRealtimeSubscription: () => { - if (this.comparisonEnabled && datasource.isAdditional && isHistoryTypeTimewindow(this.timeWindowConfig)) { - return this.updateSubscriptionForComparison(); - } else { - return this.updateRealtimeSubscription(); - } + return this.updateRealtimeSubscription(); }, setRealtimeSubscription: (subscriptionTimewindow) => { - if (this.comparisonEnabled && datasource.isAdditional && isHistoryTypeTimewindow(this.timeWindowConfig)) { - this.updateSubscriptionForComparison(subscriptionTimewindow); - } else { - this.updateRealtimeSubscription(deepClone(subscriptionTimewindow)); - } + this.updateRealtimeSubscription(deepClone(subscriptionTimewindow)); } }; this.entityDataListeners.push(listener); @@ -585,8 +578,9 @@ export class WidgetSubscription implements IWidgetSubscription { if (this.type === widgetType.timeseries || this.type === widgetType.alarm) { if (this.useDashboardTimewindow) { if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newDashboardTimewindow); this.timeWindowConfig = deepClone(newDashboardTimewindow); - this.update(); + this.update(isTimewindowTypeChanged); } } } else if (this.type === widgetType.latest) { @@ -615,8 +609,9 @@ export class WidgetSubscription implements IWidgetSubscription { updateTimewindowConfig(newTimewindow: Timewindow): void { if (!this.useDashboardTimewindow) { + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newTimewindow); this.timeWindowConfig = newTimewindow; - this.update(); + this.update(isTimewindowTypeChanged); } } @@ -625,10 +620,11 @@ export class WidgetSubscription implements IWidgetSubscription { this.ctx.dashboardTimewindowApi.onResetTimewindow(); } else { if (this.originalTimewindow) { + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow); this.timeWindowConfig = deepClone(this.originalTimewindow); this.originalTimewindow = null; this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); - this.update(); + this.update(isTimewindowTypeChanged); } } } @@ -642,7 +638,8 @@ export class WidgetSubscription implements IWidgetSubscription { } this.timeWindowConfig = toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs, interval, this.ctx.timeService); this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); - this.update(); + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow); + this.update(isTimewindowTypeChanged); } } @@ -771,16 +768,20 @@ export class WidgetSubscription implements IWidgetSubscription { } } - update() { + update(isTimewindowTypeChanged = false) { if (this.type !== widgetType.rpc) { if (this.type === widgetType.alarm) { this.updateAlarmDataSubscription(); } else { - if (this.hasDataPageLink) { - this.updateDataSubscriptions(); + if (this.type === widgetType.timeseries && this.options.comparisonEnabled && isTimewindowTypeChanged) { + this.forceReInit(); } else { - this.notifyDataLoading(); - this.dataSubscribe(); + if (this.hasDataPageLink) { + this.updateDataSubscriptions(); + } else { + this.notifyDataLoading(); + this.dataSubscribe(); + } } } } @@ -889,7 +890,7 @@ export class WidgetSubscription implements IWidgetSubscription { if (!this.hasDataPageLink) { if (this.type === widgetType.timeseries && this.timeWindowConfig) { this.updateRealtimeSubscription(); - if (this.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig)) { + if (this.comparisonEnabled) { this.updateSubscriptionForComparison(); } } @@ -905,7 +906,7 @@ export class WidgetSubscription implements IWidgetSubscription { const forceUpdate = !this.datasources.length; const notifyDataLoaded = !this.entityDataListeners.filter((listener) => listener.subscription ? true : false).length; this.entityDataListeners.forEach((listener) => { - if (this.comparisonEnabled && listener.configDatasource.isAdditional && isHistoryTypeTimewindow(this.timeWindowConfig)) { + if (this.comparisonEnabled && listener.configDatasource.isAdditional) { listener.subscriptionTimewindow = this.timewindowForComparison; } else { listener.subscriptionTimewindow = this.subscriptionTimewindow; @@ -1144,24 +1145,14 @@ export class WidgetSubscription implements IWidgetSubscription { private updateComparisonTimewindow() { this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone; - if (this.timewindowForComparison.realtimeWindowMs) { - this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); - this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf(); - } else if (this.timewindowForComparison.fixedWindow) { - this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs; - this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs; + if (this.timewindowForComparison.fixedWindow) { + this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset; + this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset; } } - private updateSubscriptionForComparison(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow { - if (subscriptionTimewindow) { - this.timewindowForComparison = subscriptionTimewindow; - } else { - if (!this.subscriptionTimewindow) { - this.subscriptionTimewindow = this.updateRealtimeSubscription(); - } - this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison); - } + private updateSubscriptionForComparison(): SubscriptionTimewindow { + this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison); this.updateComparisonTimewindow(); return this.timewindowForComparison; } @@ -1170,6 +1161,10 @@ export class WidgetSubscription implements IWidgetSubscription { this.callbacks.onInitialPageDataChanged(this, nextPageData); } + private forceReInit() { + this.callbacks.forceReInit(); + } + private dataLoaded(pageData: PageData, data: Array>, datasourceIndex: number, pageLink: EntityDataPageLink, isUpdate: boolean) { @@ -1262,7 +1257,7 @@ export class WidgetSubscription implements IWidgetSubscription { index++; }); }); - if (this.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig)) { + if (this.comparisonEnabled) { this.datasourcePages.forEach(datasourcePage => { datasourcePage.data.forEach((datasource, dIndex) => { if (datasource.isAdditional) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts index 3d60934c0f..836897740a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts @@ -20,6 +20,7 @@ import { DataKey, Datasource, DatasourceData, JsonSettingsSchema } from '@shared/models/widget.models'; import * as moment_ from 'moment'; import { DataKeyType } from "@shared/models/telemetry/telemetry.models"; +import { ComparisonDuration } from '@shared/models/time/time.models'; export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph'; @@ -142,7 +143,7 @@ export interface TbFlotBaseSettings { export interface TbFlotComparisonSettings { comparisonEnabled: boolean; - timeForComparison: moment_.unitOfTime.DurationConstructor; + timeForComparison: ComparisonDuration; xaxisSecond: TbFlotSecondXAxisSettings; } @@ -543,7 +544,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = { timeForComparison: { title: 'Time to show historical data', type: 'string', - default: 'months' + default: 'previousInterval' }, xaxisSecond: { title: 'Second X axis', @@ -576,6 +577,10 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = { type: 'rc-select', multiple: false, items: [ + { + value: 'previousInterval', + label: 'Previous interval (default)' + }, { value: 'days', label: 'Day ago' @@ -586,7 +591,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = { }, { value: 'months', - label: 'Month ago (default)' + label: 'Month ago' }, { value: 'years', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 4850d08723..e82b6db24c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -74,6 +74,7 @@ export class TbFlot { private readonly utils: UtilsService; private settings: TbFlotSettings; + private comparisonEnabled: boolean; private readonly tooltip: JQuery; @@ -263,29 +264,7 @@ export class TbFlot { }; } - if (this.settings.comparisonEnabled) { - const xaxis = deepClone(this.xaxis); - xaxis.position = 'top'; - if (this.settings.xaxisSecond) { - if (this.settings.xaxisSecond.showLabels === false) { - xaxis.tickFormatter = () => { - return ''; - }; - } - xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null; - xaxis.position = this.settings.xaxisSecond.axisPosition; - } - xaxis.tickLength = 0; - this.options.xaxes.push(xaxis); - - this.options.series = { - stack: false - }; - } else { - this.options.series = { - stack: this.settings.stack === true - }; - } + this.options.series = {}; this.options.crosshair = { mode: 'x' @@ -364,7 +343,6 @@ export class TbFlot { // Experimental this.animatedPie = this.settings.animatedPie === true; - } if (this.ctx.defaultSubscription) { @@ -372,10 +350,29 @@ export class TbFlot { } } - private init($element: JQuery, subscription: IWidgetSubscription) { - this.subscription = subscription; this.$element = $element; + this.subscription = subscription; + this.comparisonEnabled = this.subscription ? this.subscription.comparisonEnabled : this.settings.comparisonEnabled; + if (this.comparisonEnabled) { + const xaxis = deepClone(this.xaxis); + xaxis.position = 'top'; + if (this.settings.xaxisSecond) { + if (this.settings.xaxisSecond.showLabels === false) { + xaxis.tickFormatter = () => { + return ''; + }; + } + xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null; + xaxis.position = this.settings.xaxisSecond.axisPosition; + } + xaxis.tickLength = 0; + this.options.xaxes.push(xaxis); + + this.options.series.stack = false; + } else { + this.options.series.stack = this.settings.stack === true; + } const colors: string[] = []; this.yaxes = []; const yaxesMap: {[units: string]: TbFlotAxisOptions} = {}; @@ -387,7 +384,7 @@ export class TbFlot { this.settings.dataKeysListForLabels.forEach((item) => { item.settings = {}; }); - subscription.datasources.forEach((item) => { + this.subscription.datasources.forEach((item) => { const datasource: Datasource = { type: item.type, entityType: item.entityType, @@ -425,7 +422,7 @@ export class TbFlot { fill: keySettings.fillLines === true }; - if (this.settings.stack && !this.settings.comparisonEnabled) { + if (this.settings.stack && !this.comparisonEnabled) { series.stack = !keySettings.excludeFromStacking; } else { series.stack = false; @@ -557,7 +554,7 @@ export class TbFlot { } this.options.xaxes[0].min = this.subscription.timeWindow.minTime; this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; - if (this.settings.comparisonEnabled) { + if (this.comparisonEnabled) { this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; } @@ -636,7 +633,7 @@ export class TbFlot { this.options.xaxes[0].min = this.subscription.timeWindow.minTime; this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; - if (this.settings.comparisonEnabled) { + if (this.comparisonEnabled) { this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; } @@ -654,7 +651,7 @@ export class TbFlot { } else { this.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; this.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime; - if (this.settings.comparisonEnabled) { + if (this.comparisonEnabled) { this.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; this.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; } @@ -1293,7 +1290,7 @@ export class TbFlot { const results: TbFlotHoverInfo[] = [{ seriesHover: [] }]; - if (this.settings.comparisonEnabled) { + if (this.comparisonEnabled) { results.push({ seriesHover: [] }); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index e05de53e79..53419f2fdd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -857,6 +857,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI onInitialPageDataChanged: (subscription, nextPageData) => { this.reInit(); }, + forceReInit: () => { + this.reInit(); + }, dataLoading: (subscription) => { if (this.loadingData !== subscription.loadingData) { this.loadingData = subscription.loadingData; diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index 6c127f6232..912fcbff45 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -28,6 +28,8 @@ export const DAY = 24 * HOUR; export const WEEK = 7 * DAY; export const YEAR = DAY * 365; +export type ComparisonDuration = moment_.unitOfTime.DurationConstructor | 'previousInterval'; + export enum TimewindowType { REALTIME, HISTORY @@ -118,7 +120,7 @@ export interface SubscriptionTimewindow { realtimeWindowMs?: number; fixedWindow?: FixedWindow; aggregation?: SubscriptionAggregation; - timeForComparison?: moment_.unitOfTime.DurationConstructor; + timeForComparison?: ComparisonDuration; } export interface WidgetTimewindow { @@ -319,6 +321,15 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, return historyTimewindow; } +export function timewindowTypeChanged(newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean { + if (!newTimewindow || !oldTimewindow) { + return false; + } + const newType = getTimewindowType(newTimewindow); + const oldType = getTimewindowType(oldTimewindow); + return newType !== oldType; +} + export function calculateTsOffset(timezone?: string): number { if (timezone) { const tz = getTimezone(timezone); @@ -555,8 +566,78 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { } } +export function calculateIntervalComparisonStartTime(interval: QuickTimeInterval, + currentDate: moment_.Moment): number { + switch (interval) { + case QuickTimeInterval.YESTERDAY: + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: + case QuickTimeInterval.CURRENT_DAY: + case QuickTimeInterval.CURRENT_DAY_SO_FAR: + currentDate.subtract(1, 'days'); + return currentDate.startOf('day').valueOf(); + case QuickTimeInterval.THIS_DAY_LAST_WEEK: + currentDate.subtract(1, 'weeks'); + return currentDate.startOf('day').valueOf(); + case QuickTimeInterval.PREVIOUS_WEEK: + case QuickTimeInterval.CURRENT_WEEK: + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + currentDate.subtract(1, 'weeks'); + return currentDate.startOf('week').valueOf(); + case QuickTimeInterval.PREVIOUS_MONTH: + case QuickTimeInterval.CURRENT_MONTH: + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + currentDate.subtract(1, 'months'); + return currentDate.startOf('month').valueOf(); + case QuickTimeInterval.PREVIOUS_YEAR: + case QuickTimeInterval.CURRENT_YEAR: + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + currentDate.subtract(1, 'years'); + return currentDate.startOf('year').valueOf(); + case QuickTimeInterval.CURRENT_HOUR: + currentDate.subtract(1, 'hour'); + return currentDate.startOf('hour').valueOf(); + } +} + +export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval, + currentDate: moment_.Moment): number { + switch (interval) { + case QuickTimeInterval.YESTERDAY: + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: + case QuickTimeInterval.CURRENT_DAY: + currentDate.subtract(1, 'days'); + return currentDate.endOf('day').valueOf(); + case QuickTimeInterval.CURRENT_DAY_SO_FAR: + return currentDate.subtract(1, 'days').valueOf(); + case QuickTimeInterval.THIS_DAY_LAST_WEEK: + currentDate.subtract(1, 'weeks'); + return currentDate.endOf('day').valueOf(); + case QuickTimeInterval.PREVIOUS_WEEK: + case QuickTimeInterval.CURRENT_WEEK: + currentDate.subtract(1, 'weeks'); + return currentDate.endOf('week').valueOf(); + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + return currentDate.subtract(1, 'week').valueOf(); + case QuickTimeInterval.PREVIOUS_MONTH: + case QuickTimeInterval.CURRENT_MONTH: + currentDate.subtract(1, 'months'); + return currentDate.endOf('month').valueOf(); + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + return currentDate.subtract(1, 'month').valueOf(); + case QuickTimeInterval.PREVIOUS_YEAR: + case QuickTimeInterval.CURRENT_YEAR: + currentDate.subtract(1, 'years'); + return currentDate.endOf('year').valueOf(); + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + return currentDate.subtract(1, 'year').valueOf(); + case QuickTimeInterval.CURRENT_HOUR: + currentDate.subtract(1, 'hour'); + return currentDate.endOf('hour').valueOf(); + } +} + export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow, - timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow { + timeUnit: ComparisonDuration): SubscriptionTimewindow { const timewindowForComparison: SubscriptionTimewindow = { fixedWindow: null, realtimeWindowMs: null, @@ -564,18 +645,30 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti tsOffset: subscriptionTimewindow.tsOffset }; - if (subscriptionTimewindow.realtimeWindowMs) { - if (subscriptionTimewindow.quickInterval) { - timewindowForComparison.quickInterval = subscriptionTimewindow.quickInterval; - timewindowForComparison.timeForComparison = timeUnit; + if (subscriptionTimewindow.fixedWindow) { + let startTimeMs; + let endTimeMs; + if (timeUnit === 'previousInterval') { + if (subscriptionTimewindow.quickInterval) { + const startDate = moment(subscriptionTimewindow.fixedWindow.startTimeMs); + const endDate = moment(subscriptionTimewindow.fixedWindow.endTimeMs); + if (subscriptionTimewindow.timezone) { + startDate.tz(subscriptionTimewindow.timezone); + endDate.tz(subscriptionTimewindow.timezone); + } + startTimeMs = calculateIntervalComparisonStartTime(subscriptionTimewindow.quickInterval, startDate); + endTimeMs = calculateIntervalComparisonEndTime(subscriptionTimewindow.quickInterval, endDate); + } else { + const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; + endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs; + startTimeMs = endTimeMs - timeInterval; + } + } else { + const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; + endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf(); + startTimeMs = endTimeMs - timeInterval; } - timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf(); - timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs; - } else if (subscriptionTimewindow.fixedWindow) { - const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; - const endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf(); - - timewindowForComparison.startTs = endTimeMs - timeInterval; + timewindowForComparison.startTs = startTimeMs; timewindowForComparison.fixedWindow = { startTimeMs: timewindowForComparison.startTs, endTimeMs diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 8b4b6b14af..5edf291838 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1798,6 +1798,7 @@ "avg": "avg", "total": "total", "comparison-time-ago": { + "previousInterval": "(previous interval)", "days": "(day ago)", "weeks": "(week ago)", "months": "(month ago)",