UI: Added new history time window type - interval

This commit is contained in:
Vladyslav_Prykhodko 2021-03-15 16:29:42 +02:00
parent 430e96cdb2
commit f1193c1d5b
9 changed files with 331 additions and 12 deletions

View File

@ -0,0 +1,27 @@
<!--
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.
-->
<section class="interval-section" fxLayout="row" fxFlex>
<mat-form-field fxFlex>
<mat-label translate>timewindow.interval</mat-label>
<mat-select [disabled]="disabled" [(ngModel)]="modelValue" (ngModelChange)="onIntervalChange()">
<mat-option *ngFor="let interval of intervals" [value]="interval">
{{ timeIntervalTranslationMap.get(interval) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
</section>

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -65,6 +65,17 @@
style="padding-top: 8px;"></tb-datetime-period>
</section>
</mat-radio-button>
<mat-radio-button [value]="historyTypes.INTERVAL" color="primary">
<section fxLayout="column">
<span translate>timewindow.interval</span>
<tb-quick-time-interval
formControlName="quickInterval"
[fxShow]="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
[required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval>
</section>
</mat-radio-button>
</mat-radio-group>
</div>
</section>

View File

@ -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();
}

View File

@ -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');

View File

@ -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, string>([
[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<TimeInterval>(
{
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<TimeInterval>(
{
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<TimeInterval>(
{
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<TimeInterval>(
{
name: 'timeinterval.days-interval',
translateParams: {days: 1},
value: 1 * DAY
value: DAY
},
{
name: 'timeinterval.days-interval',

View File

@ -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,

View File

@ -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",