Browse Source

UI: Added new history time window type - interval

pull/4251/head
Vladyslav_Prykhodko 6 years ago
parent
commit
f1193c1d5b
  1. 27
      ui-ngx/src/app/shared/components/time/quick-time-interval.component.html
  2. 19
      ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss
  3. 80
      ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts
  4. 11
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.html
  5. 16
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts
  6. 3
      ui-ngx/src/app/shared/components/time/timewindow.component.ts
  7. 163
      ui-ngx/src/app/shared/models/time/time.models.ts
  8. 3
      ui-ngx/src/app/shared/shared.module.ts
  9. 21
      ui-ngx/src/assets/locale/locale.constant-en_US.json

27
ui-ngx/src/app/shared/components/time/quick-time-interval.component.html

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

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

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

11
ui-ngx/src/app/shared/components/time/timewindow-panel.component.html

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

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

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

163
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, 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',

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

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

Loading…
Cancel
Save