diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html new file mode 100644 index 0000000000..c9f367c574 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.html @@ -0,0 +1,27 @@ + +
+ + timewindow.interval + + + {{ timeIntervalTranslationMap.get(interval) | translate}} + + + +
diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss new file mode 100644 index 0000000000..97c400525e --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + min-width: 364px; +} diff --git a/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts new file mode 100644 index 0000000000..7f0ccb90db --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts @@ -0,0 +1,80 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { QuickTimeInterval, QuickTimeIntervalTranslationMap } from '@shared/models/time/time.models'; + +@Component({ + selector: 'tb-quick-time-interval', + templateUrl: './quick-time-interval.component.html', + styleUrls: ['./quick-time-interval.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => QuickTimeIntervalComponent), + multi: true + } + ] +}) +export class QuickTimeIntervalComponent implements OnInit, ControlValueAccessor { + + private allIntervals = Object.values(QuickTimeInterval); + + modelValue: QuickTimeInterval; + timeIntervalTranslationMap = QuickTimeIntervalTranslationMap; + + rendered = false; + + @Input() disabled: boolean; + + @Input() onlyCurrentInterval = false; + + private propagateChange = (_: any) => {}; + + constructor() { + } + + get intervals() { + if (this.onlyCurrentInterval) { + return this.allIntervals.filter(interval => interval.startsWith('TODAY_') || interval.startsWith('CURRENT_')); + } + return this.allIntervals; + } + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(interval: QuickTimeInterval): void { + this.modelValue = interval; + this.rendered = true; + } + + onIntervalChange() { + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index e4ddaa0f56..432b84749a 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -65,6 +65,17 @@ style="padding-top: 8px;"> + +
+ timewindow.interval + +
+
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index aeb9c265e1..b4aefef859 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -19,7 +19,7 @@ import { aggregationTranslations, AggregationType, DAY, - HistoryWindowType, + HistoryWindowType, quickTimeIntervalPeriod, Timewindow, TimewindowType } from '@shared/models/time/time.models'; @@ -119,7 +119,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { value: this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined' ? this.timewindow.history.fixedTimewindow : null, disabled: hideInterval - }) + }), + quickInterval: this.fb.control({ + value: this.timewindow.history && typeof this.timewindow.history.quickInterval !== 'undefined' + ? this.timewindow.history.quickInterval : null, + disabled: hideInterval + }), } ), aggregation: this.fb.group( @@ -149,7 +154,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { historyType: timewindowFormValue.history.historyType, timewindowMs: timewindowFormValue.history.timewindowMs, interval: timewindowFormValue.history.interval, - fixedTimewindow: timewindowFormValue.history.fixedTimewindow + fixedTimewindow: timewindowFormValue.history.fixedTimewindow, + quickInterval: timewindowFormValue.history.quickInterval }; if (this.aggregation) { this.timewindow.aggregation = { @@ -193,6 +199,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { const timewindowFormValue = this.timewindowForm.getRawValue(); if (timewindowFormValue.history.historyType === HistoryWindowType.LAST_INTERVAL) { return timewindowFormValue.history.timewindowMs; + } else if (timewindowFormValue.history.historyType === HistoryWindowType.INTERVAL) { + return quickTimeIntervalPeriod(timewindowFormValue.history.quickInterval); } else if (timewindowFormValue.history.fixedTimewindow) { return timewindowFormValue.history.fixedTimewindow.endTimeMs - timewindowFormValue.history.fixedTimewindow.startTimeMs; @@ -206,10 +214,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timewindowForm.get('history.historyType').disable({emitEvent: false}); this.timewindowForm.get('history.timewindowMs').disable({emitEvent: false}); this.timewindowForm.get('history.fixedTimewindow').disable({emitEvent: false}); + this.timewindowForm.get('history.quickInterval').disable({emitEvent: false}); } else { this.timewindowForm.get('history.historyType').enable({emitEvent: false}); this.timewindowForm.get('history.timewindowMs').enable({emitEvent: false}); this.timewindowForm.get('history.fixedTimewindow').enable({emitEvent: false}); + this.timewindowForm.get('history.quickInterval').enable({emitEvent: false}); } this.timewindowForm.markAsDirty(); } diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.ts b/ui-ngx/src/app/shared/components/time/timewindow.component.ts index e1be42eff3..59eada88a9 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.ts @@ -33,6 +33,7 @@ import { cloneSelectedTimewindow, HistoryWindowType, initModelFromDefaultTimewindow, + QuickTimeIntervalTranslationMap, Timewindow, TimewindowType } from '@shared/models/time/time.models'; @@ -280,6 +281,8 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces if (this.innerValue.history.historyType === HistoryWindowType.LAST_INTERVAL) { this.innerValue.displayValue += this.translate.instant('timewindow.last-prefix') + ' ' + this.millisecondsToTimeStringPipe.transform(this.innerValue.history.timewindowMs); + } else if (this.innerValue.history.historyType === HistoryWindowType.INTERVAL) { + this.innerValue.displayValue += this.translate.instant(QuickTimeIntervalTranslationMap.get(this.innerValue.history.quickInterval)); } else { const startString = this.datePipe.transform(this.innerValue.history.fixedTimewindow.startTimeMs, 'yyyy-MM-dd HH:mm:ss'); const endString = this.datePipe.transform(this.innerValue.history.fixedTimewindow.endTimeMs, 'yyyy-MM-dd HH:mm:ss'); 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 1156840915..ee3748e214 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -27,6 +27,7 @@ export const SECOND = 1000; export const MINUTE = 60 * SECOND; export const HOUR = 60 * MINUTE; export const DAY = 24 * HOUR; +export const WEEK = 7 * DAY; export const YEAR = DAY * 365; export enum TimewindowType { @@ -36,12 +37,14 @@ export enum TimewindowType { export enum HistoryWindowType { LAST_INTERVAL, - FIXED + FIXED, + INTERVAL } export interface IntervalWindow { interval?: number; timewindowMs?: number; + quickInterval?: QuickTimeInterval; } export interface FixedWindow { @@ -111,6 +114,40 @@ export interface WidgetTimewindow { stDiff?: number; } +export enum QuickTimeInterval { + YESTERDAY = 'YESTERDAY', + DAY_BEFORE_YESTERDAY = 'DAY_BEFORE_YESTERDAY', + THIS_DAY_LAST_WEEK = 'THIS_DAY_LAST_WEEK', + PREVIOUS_WEEK = 'PREVIOUS_WEEK', + PREVIOUS_MONTH = 'PREVIOUS_MONTH', + PREVIOUS_YEAR = 'PREVIOUS_YEAR', + TODAY = 'TODAY', + TODAY_SO_FAR = 'TODAY_SO_FAR', + CURRENT_WEEK = 'CURRENT_WEEK', + CURRENT_WEEK_SO_FAR = 'CURRENT_WEEK_SO_WAR', + CURRENT_MONTH = 'CURRENT_MONTH', + CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR', + CURRENT_YEAR = 'CURRENT_YEAR', + CURRENT_YEAR_SO_FAR = 'CURRENT_YEAR_SO_FAR' +} + +export const QuickTimeIntervalTranslationMap = new Map([ + [QuickTimeInterval.YESTERDAY, 'timeinterval.predefined.yesterday'], + [QuickTimeInterval.DAY_BEFORE_YESTERDAY, 'timeinterval.predefined.day-before-yesterday'], + [QuickTimeInterval.THIS_DAY_LAST_WEEK, 'timeinterval.predefined.this-day-last-week'], + [QuickTimeInterval.PREVIOUS_WEEK, 'timeinterval.predefined.previous-week'], + [QuickTimeInterval.PREVIOUS_MONTH, 'timeinterval.predefined.previous-month'], + [QuickTimeInterval.PREVIOUS_YEAR, 'timeinterval.predefined.previous-year'], + [QuickTimeInterval.TODAY, 'timeinterval.predefined.today'], + [QuickTimeInterval.TODAY_SO_FAR, 'timeinterval.predefined.today-so-far'], + [QuickTimeInterval.CURRENT_WEEK, 'timeinterval.predefined.current-week'], + [QuickTimeInterval.CURRENT_WEEK_SO_FAR, 'timeinterval.predefined.current-week-so-far'], + [QuickTimeInterval.CURRENT_MONTH, 'timeinterval.predefined.current-month'], + [QuickTimeInterval.CURRENT_MONTH_SO_FAR, 'timeinterval.predefined.current-month-so-far'], + [QuickTimeInterval.CURRENT_YEAR, 'timeinterval.predefined.current-year'], + [QuickTimeInterval.CURRENT_YEAR_SO_FAR, 'timeinterval.predefined.current-year-so-far'] +]); + export function historyInterval(timewindowMs: number): Timewindow { const timewindow: Timewindow = { selectedTab: TimewindowType.HISTORY, @@ -141,7 +178,8 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { fixedTimewindow: { startTimeMs: currentTime - DAY, endTimeMs: currentTime - } + }, + quickInterval: QuickTimeInterval.TODAY }, aggregation: { type: AggregationType.AVG, @@ -178,6 +216,8 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T if (isUndefined(value.history.historyType)) { if (isDefined(value.history.timewindowMs)) { model.history.historyType = HistoryWindowType.LAST_INTERVAL; + } else if (isDefined(value.history.quickInterval)) { + model.history.historyType = HistoryWindowType.INTERVAL; } else { model.history.historyType = HistoryWindowType.FIXED; } @@ -186,6 +226,8 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T } if (model.history.historyType === HistoryWindowType.LAST_INTERVAL) { model.history.timewindowMs = value.history.timewindowMs; + } else if (model.history.historyType === HistoryWindowType.INTERVAL) { + model.history.quickInterval = value.history.quickInterval; } else { model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs; model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs; @@ -281,7 +323,13 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num } else { let historyType = timewindow.history.historyType; if (isUndefined(historyType)) { - historyType = isDefined(timewindow.history.timewindowMs) ? HistoryWindowType.LAST_INTERVAL : HistoryWindowType.FIXED; + if (isDefined(timewindow.history.timewindowMs)) { + historyType = HistoryWindowType.LAST_INTERVAL; + } else if (isDefined(timewindow.history.quickInterval)) { + historyType = HistoryWindowType.INTERVAL; + } else { + historyType = HistoryWindowType.FIXED; + } } if (historyType === HistoryWindowType.LAST_INTERVAL) { const currentTime = Date.now(); @@ -290,6 +338,9 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num endTimeMs: currentTime }; aggTimewindow = timewindow.history.timewindowMs; + } else if (historyType === HistoryWindowType.INTERVAL) { + subscriptionTimewindow.fixedWindow = createSubscriptionTimeWindowFromQuickKTimeInterval(timewindow.history.quickInterval); + aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; } else { subscriptionTimewindow.fixedWindow = { startTimeMs: timewindow.history.fixedTimewindow.startTimeMs, @@ -309,6 +360,102 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num return subscriptionTimewindow; } +export function createSubscriptionTimeWindowFromQuickKTimeInterval(interval: QuickTimeInterval): FixedWindow { + const currentDate = moment(); + const timeWindow = { + startTimeMs: 0, + endTimeMs: 0 + }; + switch (interval) { + case QuickTimeInterval.YESTERDAY: + currentDate.subtract(1, 'days'); + timeWindow.startTimeMs = currentDate.startOf('day').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('day').valueOf(); + break; + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: + currentDate.subtract(2, 'days'); + timeWindow.startTimeMs = currentDate.startOf('day').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('day').valueOf(); + break; + case QuickTimeInterval.THIS_DAY_LAST_WEEK: + currentDate.subtract(1, 'weeks'); + timeWindow.startTimeMs = currentDate.startOf('day').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('day').valueOf(); + break; + case QuickTimeInterval.PREVIOUS_WEEK: + currentDate.subtract(1, 'weeks'); + timeWindow.startTimeMs = currentDate.startOf('week').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('week').valueOf(); + break; + case QuickTimeInterval.PREVIOUS_MONTH: + currentDate.subtract(1, 'months'); + timeWindow.startTimeMs = currentDate.startOf('month').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('month').valueOf(); + break; + case QuickTimeInterval.PREVIOUS_YEAR: + currentDate.subtract(1, 'years'); + timeWindow.startTimeMs = currentDate.startOf('year').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('year').valueOf(); + break; + case QuickTimeInterval.TODAY: + timeWindow.startTimeMs = currentDate.startOf('day').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('day').valueOf(); + break; + case QuickTimeInterval.TODAY_SO_FAR: + timeWindow.endTimeMs = currentDate.valueOf(); + timeWindow.startTimeMs = currentDate.startOf('day').valueOf(); + break; + case QuickTimeInterval.CURRENT_WEEK: + timeWindow.startTimeMs = currentDate.startOf('week').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('week').valueOf(); + break; + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + timeWindow.endTimeMs = currentDate.valueOf(); + timeWindow.startTimeMs = currentDate.startOf('week').valueOf(); + break; + case QuickTimeInterval.CURRENT_MONTH: + timeWindow.startTimeMs = currentDate.startOf('month').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('month').valueOf(); + break; + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + timeWindow.endTimeMs = currentDate.valueOf(); + timeWindow.startTimeMs = currentDate.startOf('month').valueOf(); + break; + case QuickTimeInterval.CURRENT_YEAR: + timeWindow.startTimeMs = currentDate.startOf('year').valueOf(); + timeWindow.endTimeMs = currentDate.endOf('year').valueOf(); + break; + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + timeWindow.endTimeMs = currentDate.valueOf(); + timeWindow.startTimeMs = currentDate.startOf('year').valueOf(); + break; + } + return timeWindow; +} + +export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { + switch (interval) { + case QuickTimeInterval.YESTERDAY: + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: + case QuickTimeInterval.THIS_DAY_LAST_WEEK: + case QuickTimeInterval.TODAY: + case QuickTimeInterval.TODAY_SO_FAR: + return DAY; + case QuickTimeInterval.PREVIOUS_WEEK: + case QuickTimeInterval.CURRENT_WEEK: + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: + return WEEK; + case QuickTimeInterval.PREVIOUS_MONTH: + case QuickTimeInterval.CURRENT_MONTH: + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: + return DAY * 30; + case QuickTimeInterval.PREVIOUS_YEAR: + case QuickTimeInterval.CURRENT_YEAR: + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: + return YEAR; + } +} + export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow, timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow { const timewindowForComparison: SubscriptionTimewindow = { @@ -358,6 +505,8 @@ export function cloneSelectedHistoryTimewindow(historyWindow: HistoryWindow): Hi cloned.interval = historyWindow.interval; if (historyWindow.historyType === HistoryWindowType.LAST_INTERVAL) { cloned.timewindowMs = historyWindow.timewindowMs; + } else if (historyWindow.historyType === HistoryWindowType.INTERVAL) { + cloned.quickInterval = historyWindow.quickInterval; } else if (historyWindow.historyType === HistoryWindowType.FIXED) { cloned.fixedTimewindow = deepClone(historyWindow.fixedTimewindow); } @@ -375,7 +524,7 @@ export const defaultTimeIntervals = new Array( { name: 'timeinterval.seconds-interval', translateParams: {seconds: 1}, - value: 1 * SECOND + value: SECOND }, { name: 'timeinterval.seconds-interval', @@ -400,7 +549,7 @@ export const defaultTimeIntervals = new Array( { name: 'timeinterval.minutes-interval', translateParams: {minutes: 1}, - value: 1 * MINUTE + value: MINUTE }, { name: 'timeinterval.minutes-interval', @@ -430,7 +579,7 @@ export const defaultTimeIntervals = new Array( { name: 'timeinterval.hours-interval', translateParams: {hours: 1}, - value: 1 * HOUR + value: HOUR }, { name: 'timeinterval.hours-interval', @@ -455,7 +604,7 @@ export const defaultTimeIntervals = new Array( { name: 'timeinterval.days-interval', translateParams: {days: 1}, - value: 1 * DAY + value: DAY }, { name: 'timeinterval.days-interval', diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 6b9353b8ae..02cd3b93d4 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -140,6 +140,7 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select import { FileSizePipe } from '@shared/pipe/file-size.pipe'; import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-search.component'; import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; +import { QuickTimeIntervalComponent } from '@shared/components/time/quick-time-interval.component'; @NgModule({ providers: [ @@ -175,6 +176,7 @@ import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; TimewindowComponent, TimewindowPanelComponent, TimeintervalComponent, + QuickTimeIntervalComponent, DashboardSelectComponent, DashboardSelectPanelComponent, DatetimePeriodComponent, @@ -302,6 +304,7 @@ import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; TimewindowComponent, TimewindowPanelComponent, TimeintervalComponent, + QuickTimeIntervalComponent, DashboardSelectComponent, DatetimePeriodComponent, DatetimeComponent, 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 ec8c1458ef..7508dac060 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2111,7 +2111,23 @@ "hours": "Hours", "minutes": "Minutes", "seconds": "Seconds", - "advanced": "Advanced" + "advanced": "Advanced", + "predefined": { + "yesterday": "Yesterday", + "day-before-yesterday": "Day before yesterday", + "this-day-last-week": "This day last week", + "previous-week": "Previous week", + "previous-month": "Previous month", + "previous-year": "Previous year", + "today": "Today", + "today-so-far": "Today so far", + "current-week": "Current week", + "current-week-so-far": "Current week so far", + "current-month": "Current month", + "current-month-so-far": "Current month so far", + "current-year": "Current year", + "current-year-so-far": "Current year so far" + } }, "timeunit": { "seconds": "Seconds", @@ -2132,7 +2148,8 @@ "date-range": "Date range", "last": "Last", "time-period": "Time period", - "hide": "Hide" + "hide": "Hide", + "interval": "Interval" }, "user": { "user": "User",