Browse Source

Merge branch 'vvlladd28-feature/timeWindow/quick-interval'

pull/4259/head
Igor Kulikov 5 years ago
parent
commit
7cfa352a60
  1. 74
      ui-ngx/src/app/core/api/data-aggregator.ts
  2. 36
      ui-ngx/src/app/core/api/entity-data-subscription.ts
  3. 32
      ui-ngx/src/app/core/api/widget-subscription.ts
  4. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  5. 1
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html
  6. 12
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  7. 9
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts
  8. 27
      ui-ngx/src/app/shared/components/time/quick-time-interval.component.html
  9. 19
      ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss
  10. 79
      ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts
  11. 68
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.html
  12. 76
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts
  13. 28
      ui-ngx/src/app/shared/components/time/timewindow.component.ts
  14. 57
      ui-ngx/src/app/shared/components/time/timezone-select.component.ts
  15. 41
      ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
  16. 377
      ui-ngx/src/app/shared/models/time/time.models.ts
  17. 3
      ui-ngx/src/app/shared/shared.module.ts
  18. 22
      ui-ngx/src/assets/locale/locale.constant-en_US.json

74
ui-ngx/src/app/core/api/data-aggregator.ts

@ -15,7 +15,12 @@
///
import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models';
import { AggregationType } from '@shared/models/time/time.models';
import {
AggregationType,
calculateIntervalEndTime,
calculateIntervalStartTime, getCurrentTime,
QuickTimeInterval, SubscriptionTimewindow
} from '@shared/models/time/time.models';
import { UtilsService } from '@core/services/utils.service';
import { deepClone } from '@core/utils';
import Timeout = NodeJS.Timeout;
@ -73,33 +78,29 @@ export class DataAggregator {
private resetPending = false;
private updatedData = false;
private noAggregation = this.aggregationType === AggregationType.NONE;
private aggregationTimeout = Math.max(this.interval, 1000);
private noAggregation = this.subsTw.aggregation.type === AggregationType.NONE;
private aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
private readonly aggFunction: AggFunction;
private intervalTimeoutHandle: Timeout;
private intervalScheduledTime: number;
private startTs = this.subsTw.startTs + this.subsTw.tsOffset;
private endTs: number;
private elapsed: number;
constructor(private onDataCb: onAggregatedData,
private tsKeyNames: string[],
private startTs: number,
private limit: number,
private aggregationType: AggregationType,
private timeWindow: number,
private interval: number,
private stateData: boolean,
private subsTw: SubscriptionTimewindow,
private utils: UtilsService,
private ignoreDataUpdateOnIntervalTick: boolean) {
this.tsKeyNames.forEach((key) => {
this.dataBuffer[key] = [];
});
if (this.stateData) {
if (this.subsTw.aggregation.stateData) {
this.lastPrevKvPairData = {};
}
switch (this.aggregationType) {
switch (this.subsTw.aggregation.type) {
case AggregationType.MIN:
this.aggFunction = min;
break;
@ -129,18 +130,21 @@ export class DataAggregator {
return prevOnDataCb;
}
public reset(startTs: number, timeWindow: number, interval: number) {
public reset(subsTw: SubscriptionTimewindow) {
if (this.intervalTimeoutHandle) {
clearTimeout(this.intervalTimeoutHandle);
this.intervalTimeoutHandle = null;
}
this.subsTw = subsTw;
this.intervalScheduledTime = this.utils.currentPerfTime();
this.startTs = startTs;
this.timeWindow = timeWindow;
this.interval = interval;
this.endTs = this.startTs + this.timeWindow;
this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;
if (this.subsTw.quickInterval) {
this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;
} else {
this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
}
this.elapsed = 0;
this.aggregationTimeout = Math.max(this.interval, 1000);
this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
this.resetPending = true;
this.updatedData = false;
this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout);
@ -161,7 +165,11 @@ export class DataAggregator {
if (!this.dataReceived) {
this.elapsed = 0;
this.dataReceived = true;
this.endTs = this.startTs + this.timeWindow;
if (this.subsTw.quickInterval) {
this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;
} else {
this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
}
}
if (this.resetPending) {
this.resetPending = false;
@ -195,12 +203,19 @@ export class DataAggregator {
this.intervalTimeoutHandle = null;
}
if (!history) {
const delta = Math.floor(this.elapsed / this.interval);
const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval);
if (delta || !this.data) {
this.startTs += delta * this.interval;
this.endTs += delta * this.interval;
const tickTs = delta * this.subsTw.aggregation.interval;
if (this.subsTw.quickInterval) {
const currentDate = getCurrentTime(this.subsTw.timezone);
this.startTs = calculateIntervalStartTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
} else {
this.startTs += tickTs;
this.endTs += tickTs;
}
this.data = this.updateData();
this.elapsed = this.elapsed - delta * this.interval;
this.elapsed = this.elapsed - delta * this.subsTw.aggregation.interval;
}
} else {
this.data = this.updateData();
@ -223,7 +238,7 @@ export class DataAggregator {
let keyData = this.dataBuffer[key];
aggKeyData.forEach((aggData, aggTimestamp) => {
if (aggTimestamp <= this.startTs) {
if (this.stateData &&
if (this.subsTw.aggregation.stateData &&
(!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) {
this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue];
}
@ -235,11 +250,11 @@ export class DataAggregator {
}
});
keyData.sort((set1, set2) => set1[0] - set2[0]);
if (this.stateData) {
if (this.subsTw.aggregation.stateData) {
this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key]));
}
if (keyData.length > this.limit) {
keyData = keyData.slice(keyData.length - this.limit);
if (keyData.length > this.subsTw.aggregation.limit) {
keyData = keyData.slice(keyData.length - this.subsTw.aggregation.limit);
}
this.dataBuffer[key] = keyData;
}
@ -275,7 +290,7 @@ export class DataAggregator {
}
private processAggregatedData(data: SubscriptionData): AggregationMap {
const isCount = this.aggregationType === AggregationType.COUNT;
const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
const aggregationMap: AggregationMap = {};
for (const key of Object.keys(data)) {
let aggKeyData = aggregationMap[key];
@ -300,7 +315,7 @@ export class DataAggregator {
}
private updateAggregatedData(data: SubscriptionData) {
const isCount = this.aggregationType === AggregationType.COUNT;
const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
for (const key of Object.keys(data)) {
let aggKeyData = this.aggregationMap[key];
if (!aggKeyData) {
@ -312,7 +327,8 @@ export class DataAggregator {
const timestamp = kvPair[0];
const value = this.convertValue(kvPair[1]);
const aggTimestamp = this.noAggregation ? timestamp : (this.startTs +
Math.floor((timestamp - this.startTs) / this.interval) * this.interval + this.interval / 2);
Math.floor((timestamp - this.startTs) / this.subsTw.aggregation.interval) *
this.subsTw.aggregation.interval + this.subsTw.aggregation.interval / 2);
let aggData = aggKeyData.get(aggTimestamp);
if (!aggData) {
aggData = {

36
ui-ngx/src/app/core/api/entity-data-subscription.ts

@ -237,7 +237,7 @@ export class EntityDataSubscription {
};
if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
this.prepareSubscriptionCommands(this.dataCommand);
this.prepareSubscriptionCommands();
}
this.subscriber.subscriptionCommands.push(this.dataCommand);
@ -256,8 +256,8 @@ export class EntityDataSubscription {
if (this.started) {
const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand;
if (this.entityDataSubscriptionOptions.type === widgetType.timeseries &&
!this.history && this.tsFields.length) {
const newSubsTw: SubscriptionTimewindow = this.listener.updateRealtimeSubscription();
!this.history && this.tsFields.length) {
const newSubsTw = this.listener.updateRealtimeSubscription();
this.subsTw = newSubsTw;
targetCommand.tsCmd.startTs = this.subsTw.startTs;
targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow;
@ -266,9 +266,10 @@ export class EntityDataSubscription {
targetCommand.tsCmd.agg = this.subsTw.aggregation.type;
targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData;
this.dataAggregators.forEach((dataAggregator) => {
dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval);
dataAggregator.reset(newSubsTw);
});
}
this.subscriber.setTsOffset(this.subsTw.tsOffset);
targetCommand.query = this.dataCommand.query;
this.subscriber.subscriptionCommands = [targetCommand];
} else {
@ -393,7 +394,7 @@ export class EntityDataSubscription {
if (this.datasourceType === DatasourceType.entity) {
this.subsCommand = new EntityDataCmd();
this.subsCommand.cmdId = this.dataCommand.cmdId;
this.prepareSubscriptionCommands(this.subsCommand);
this.prepareSubscriptionCommands();
if (!this.subsCommand.isEmpty()) {
this.subscriber.subscriptionCommands = [this.subsCommand];
this.subscriber.update();
@ -404,11 +405,11 @@ export class EntityDataSubscription {
this.started = true;
}
private prepareSubscriptionCommands(cmd: EntityDataCmd) {
private prepareSubscriptionCommands() {
if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
if (this.tsFields.length > 0) {
if (this.history) {
cmd.historyCmd = {
this.subsCommand.historyCmd = {
keys: this.tsFields.map(key => key.key),
startTs: this.subsTw.fixedWindow.startTimeMs,
endTs: this.subsTw.fixedWindow.endTimeMs,
@ -418,7 +419,7 @@ export class EntityDataSubscription {
fetchLatestPreviousPoint: this.subsTw.aggregation.stateData
};
} else {
cmd.tsCmd = {
this.subsCommand.tsCmd = {
keys: this.tsFields.map(key => key.key),
startTs: this.subsTw.startTs,
timeWindow: this.subsTw.aggregation.timeWindow,
@ -429,9 +430,10 @@ export class EntityDataSubscription {
};
}
}
this.subscriber.setTsOffset(this.subsTw.tsOffset);
} else if (this.entityDataSubscriptionOptions.type === widgetType.latest) {
if (this.latestValues.length > 0) {
cmd.latestCmd = {
this.subsCommand.latestCmd = {
keys: this.latestValues
};
}
@ -745,12 +747,7 @@ export class EntityDataSubscription {
this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb);
},
tsKeyNames,
subsTw.startTs,
subsTw.aggregation.limit,
subsTw.aggregation.type,
subsTw.aggregation.timeWindow,
subsTw.aggregation.interval,
subsTw.aggregation.stateData,
subsTw,
this.utils,
this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick
);
@ -827,7 +824,8 @@ export class EntityDataSubscription {
startTime = dataKey.lastUpdateTime + this.frequency;
endTime = dataKey.lastUpdateTime + deltaElapsed;
} else {
startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs;
startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs +
this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency;
if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) {
const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit;
@ -835,8 +833,10 @@ export class EntityDataSubscription {
}
}
} else {
startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs;
endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs;
startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs +
this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs +
this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
}
}
generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime);

32
ui-ngx/src/app/core/api/widget-subscription.ts

@ -37,8 +37,10 @@ import {
} from '@app/shared/models/widget.models';
import { HttpErrorResponse } from '@angular/common/http';
import {
calculateIntervalEndTime,
calculateIntervalStartTime,
createSubscriptionTimewindow,
createTimewindowForComparison,
createTimewindowForComparison, getCurrentTime,
SubscriptionTimewindow,
Timewindow,
toHistoryTimewindow,
@ -837,9 +839,11 @@ export class WidgetSubscription implements IWidgetSubscription {
if (this.alarmDataListener) {
this.ctx.alarmDataService.stopSubscription(this.alarmDataListener);
}
if (this.timeWindowConfig) {
this.updateRealtimeSubscription();
}
this.alarmDataListener = {
subscriptionTimewindow: this.subscriptionTimewindow,
alarmSource: this.alarmSource,
@ -1080,12 +1084,21 @@ export class WidgetSubscription implements IWidgetSubscription {
private updateTimewindow() {
this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000;
this.timeWindow.timezone = this.subscriptionTimewindow.timezone;
if (this.subscriptionTimewindow.realtimeWindowMs) {
this.timeWindow.maxTime = moment().valueOf() + this.timeWindow.stDiff;
this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs;
if (this.subscriptionTimewindow.quickInterval) {
const currentDate = getCurrentTime(this.subscriptionTimewindow.timezone);
this.timeWindow.maxTime = calculateIntervalEndTime(
this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset;
this.timeWindow.minTime = calculateIntervalStartTime(
this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset;
} else {
this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff;
this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs;
}
} else if (this.subscriptionTimewindow.fixedWindow) {
this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs;
this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs;
this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs + this.subscriptionTimewindow.tsOffset;
this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs + this.subscriptionTimewindow.tsOffset;
}
}
@ -1103,12 +1116,13 @@ 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 = this.comparisonTimeWindow.maxTime - this.timewindowForComparison.realtimeWindowMs;
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;
this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset;
this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset;
}
}
@ -1335,7 +1349,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.onDataUpdated();
}
private alarmsUpdated(_updated: Array<AlarmData>, alarms: PageData<AlarmData>) {
private alarmsUpdated(updated: Array<AlarmData>, alarms: PageData<AlarmData>) {
this.alarmsLoaded(alarms, 0, 0);
}

1
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -91,6 +91,7 @@
direction="left"
tooltipPosition="below"
aggregation="true"
timezone="true"
[(ngModel)]="dashboardCtx.dashboardTimewindow">
</tb-timewindow>
<tb-filters-edit [fxShow]="!isEdit && displayFilters()"

1
ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html

@ -95,6 +95,7 @@
<tb-timewindow *ngIf="widget.hasTimewindow"
#timewindowComponent
aggregation="{{widget.hasAggregation}}"
timezone="true"
[isEdit]="isEdit"
[(ngModel)]="widgetComponent.widget.config.timewindow"
(ngModelChange)="widgetComponent.onTimewindowChanged($event)">

12
ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts

@ -55,7 +55,13 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models';
import { DialogService } from '@core/services/dialog.service';
import { AddEntityDialogComponent } from './add-entity-dialog.component';
import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models';
import { HistoryWindowType, Timewindow } from '@shared/models/time/time.models';
import {
calculateIntervalEndTime,
calculateIntervalStartTime,
getCurrentTime,
HistoryWindowType,
Timewindow
} from '@shared/models/time/time.models';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
import { isDefined, isUndefined } from '@core/utils';
@ -296,6 +302,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
const currentTime = Date.now();
timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs;
timePageLink.endTime = currentTime;
} else if (this.timewindow.history.historyType === HistoryWindowType.INTERVAL) {
const currentDate = getCurrentTime();
timePageLink.startTime = calculateIntervalStartTime(this.timewindow.history.quickInterval, currentDate);
timePageLink.endTime = calculateIntervalEndTime(this.timewindow.history.quickInterval, currentDate);
} else {
timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs;
timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs;

9
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts

@ -94,11 +94,10 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems)
});
this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => {
getDefaultTimezone().subscribe((defaultTimezone) => {
this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false});
this.updateValidators(type, true);
this.alarmScheduleForm.updateValueAndValidity();
});
const defaultTimezone = getDefaultTimezone();
this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false});
this.updateValidators(type, true);
this.alarmScheduleForm.updateValueAndValidity();
});
this.alarmScheduleForm.valueChanges.subscribe(() => {
this.updateModel();

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

79
ui-ngx/src/app/shared/components/time/quick-time-interval.component.ts

@ -0,0 +1,79 @@
///
/// 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('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;
}
onIntervalChange() {
this.propagateChange(this.modelValue);
}
}

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

@ -21,16 +21,43 @@
<mat-tab-group dynamicHeight [ngClass]="{'tb-headless': historyOnly}"
(selectedIndexChange)="timewindowForm.markAsDirty()" [(selectedIndex)]="timewindow.selectedTab">
<mat-tab label="{{ 'timewindow.realtime' | translate }}">
<div formGroupName="realtime" class="mat-content mat-padding" fxLayout="column">
<tb-timeinterval
[(hideFlag)]="timewindow.hideInterval"
(hideFlagChange)="onHideIntervalChanged()"
[isEdit]="isEdit"
formControlName="timewindowMs"
predefinedName="timewindow.last"
[required]="timewindow.selectedTab === timewindowTypes.REALTIME"
style="padding-top: 8px;"></tb-timeinterval>
</div>
<section fxLayout="row">
<section *ngIf="isEdit" fxLayout="column" style="padding-top: 8px; padding-left: 16px;">
<label class="tb-small hide-label" translate>timewindow.hide</label>
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
</section>
<section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval">
<div formGroupName="realtime" class="mat-content mat-padding" style="padding-top: 8px;">
<mat-radio-group formControlName="realtimeType">
<mat-radio-button [value]="realtimeTypes.LAST_INTERVAL" color="primary">
<section fxLayout="column">
<span translate>timewindow.last</span>
<tb-timeinterval
formControlName="timewindowMs"
predefinedName="timewindow.last"
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL"
[required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL"
style="padding-top: 8px;"></tb-timeinterval>
</section>
</mat-radio-button>
<mat-radio-button [value]="realtimeTypes.INTERVAL" color="primary">
<section fxLayout="column">
<span translate>timewindow.interval</span>
<tb-quick-time-interval
formControlName="quickInterval"
onlyCurrentInterval="true"
[fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
[required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval>
</section>
</mat-radio-button>
</mat-radio-group>
</div>
</section>
</section>
</mat-tab>
<mat-tab label="{{ 'timewindow.history' | translate }}">
<section fxLayout="row">
@ -65,6 +92,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>
@ -139,6 +177,16 @@
predefinedName="aggregation.group-interval">
</tb-timeinterval>
</div>
<div *ngIf="timezone" class="mat-content mat-padding" fxLayout="row">
<section fxLayout="column" [fxShow]="isEdit">
<label class="tb-small hide-label" translate>timewindow.hide</label>
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideTimezone"
(ngModelChange)="onHideTimezoneChanged()"></mat-checkbox>
</section>
<tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone"
formControlName="timezone">
</tb-timezone-select>
</div>
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
<button type="button"
mat-button

76
ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts

@ -20,6 +20,8 @@ import {
AggregationType,
DAY,
HistoryWindowType,
quickTimeIntervalPeriod,
RealtimeWindowType,
Timewindow,
TimewindowType
} from '@shared/models/time/time.models';
@ -36,6 +38,7 @@ export interface TimewindowPanelData {
historyOnly: boolean;
timewindow: Timewindow;
aggregation: boolean;
timezone: boolean;
isEdit: boolean;
}
@ -50,6 +53,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
aggregation = false;
timezone = false;
isEdit = false;
timewindow: Timewindow;
@ -60,6 +65,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
historyTypes = HistoryWindowType;
realtimeTypes = RealtimeWindowType;
timewindowTypes = TimewindowType;
aggregationTypes = AggregationType;
@ -78,6 +85,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
this.historyOnly = data.historyOnly;
this.timewindow = data.timewindow;
this.aggregation = data.aggregation;
this.timezone = data.timezone;
this.isEdit = data.isEdit;
}
@ -85,10 +93,16 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
const hideInterval = this.timewindow.hideInterval || false;
const hideAggregation = this.timewindow.hideAggregation || false;
const hideAggInterval = this.timewindow.hideAggInterval || false;
const hideTimezone = this.timewindow.hideTimezone || false;
this.timewindowForm = this.fb.group({
realtime: this.fb.group(
{
realtimeType: this.fb.control({
value: this.timewindow.realtime && typeof this.timewindow.realtime.realtimeType !== 'undefined'
? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL,
disabled: hideInterval
}),
timewindowMs: [
this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined'
? this.timewindow.realtime.timewindowMs : null
@ -96,7 +110,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
interval: [
this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined'
? this.timewindow.realtime.interval : null
]
],
quickInterval: this.fb.control({
value: this.timewindow.realtime && typeof this.timewindow.realtime.quickInterval !== 'undefined'
? this.timewindow.realtime.quickInterval : null,
disabled: hideInterval
})
}
),
history: this.fb.group(
@ -119,6 +138,11 @@ 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
})
}
),
@ -135,21 +159,29 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
disabled: hideAggInterval
}, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())])
}
)
),
timezone: this.fb.control({
value: this.timewindow.timezone !== 'undefined'
? this.timewindow.timezone : null,
disabled: hideTimezone
})
});
}
update() {
const timewindowFormValue = this.timewindowForm.getRawValue();
this.timewindow.realtime = {
realtimeType: timewindowFormValue.realtime.realtimeType,
timewindowMs: timewindowFormValue.realtime.timewindowMs,
quickInterval: timewindowFormValue.realtime.quickInterval,
interval: timewindowFormValue.realtime.interval
};
this.timewindow.history = {
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 = {
@ -157,6 +189,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
limit: timewindowFormValue.aggregation.limit
};
}
if (this.timezone) {
this.timewindow.timezone = timewindowFormValue.timezone;
}
this.result = this.timewindow;
this.overlayRef.dispose();
}
@ -174,11 +209,23 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
}
minRealtimeAggInterval() {
return this.timeService.minIntervalLimit(this.timewindowForm.get('realtime.timewindowMs').value);
return this.timeService.minIntervalLimit(this.currentRealtimeTimewindow());
}
maxRealtimeAggInterval() {
return this.timeService.maxIntervalLimit(this.timewindowForm.get('realtime.timewindowMs').value);
return this.timeService.maxIntervalLimit(this.currentRealtimeTimewindow());
}
currentRealtimeTimewindow(): number {
const timeWindowFormValue = this.timewindowForm.getRawValue();
switch (timeWindowFormValue.realtime.realtimeType) {
case RealtimeWindowType.LAST_INTERVAL:
return timeWindowFormValue.realtime.timewindowMs;
case RealtimeWindowType.INTERVAL:
return quickTimeIntervalPeriod(timeWindowFormValue.realtime.quickInterval);
default:
return DAY;
}
}
minHistoryAggInterval() {
@ -193,6 +240,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 +255,18 @@ 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});
this.timewindowForm.get('realtime.realtimeType').disable({emitEvent: false});
this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false});
this.timewindowForm.get('realtime.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.get('realtime.realtimeType').enable({emitEvent: false});
this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false});
this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false});
}
this.timewindowForm.markAsDirty();
}
@ -232,4 +289,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
this.timewindowForm.markAsDirty();
}
onHideTimezoneChanged() {
if (this.timewindow.hideTimezone) {
this.timewindowForm.get('timezone').disable({emitEvent: false});
} else {
this.timewindowForm.get('timezone').enable({emitEvent: false});
}
this.timewindowForm.markAsDirty();
}
}

28
ui-ngx/src/app/shared/components/time/timewindow.component.ts

@ -33,6 +33,8 @@ import {
cloneSelectedTimewindow,
HistoryWindowType,
initModelFromDefaultTimewindow,
QuickTimeIntervalTranslationMap,
RealtimeWindowType,
Timewindow,
TimewindowType
} from '@shared/models/time/time.models';
@ -89,6 +91,17 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
return this.aggregationValue;
}
timezoneValue = false;
@Input()
set timezone(val) {
this.timezoneValue = coerceBooleanProperty(val);
}
get timezone() {
return this.timezoneValue;
}
isToolbarValue = false;
@Input()
@ -169,7 +182,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
});
if (isGtXs) {
config.minWidth = '417px';
config.maxHeight = '440px';
config.maxHeight = '500px';
const panelHeight = 375;
const panelWidth = 417;
const el = this.timewindowPanelOrigin.elementRef.nativeElement;
@ -225,6 +238,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
timewindow: deepClone(this.innerValue),
historyOnly: this.historyOnly,
aggregation: this.aggregation,
timezone: this.timezone,
isEdit: this.isEdit
}
);
@ -272,14 +286,20 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
updateDisplayValue() {
if (this.innerValue.selectedTab === TimewindowType.REALTIME && !this.historyOnly) {
this.innerValue.displayValue = this.translate.instant('timewindow.realtime') + ' - ' +
this.translate.instant('timewindow.last-prefix') + ' ' +
this.millisecondsToTimeStringPipe.transform(this.innerValue.realtime.timewindowMs);
this.innerValue.displayValue = this.translate.instant('timewindow.realtime') + ' - ';
if (this.innerValue.realtime.realtimeType === RealtimeWindowType.INTERVAL) {
this.innerValue.displayValue += this.translate.instant(QuickTimeIntervalTranslationMap.get(this.innerValue.realtime.quickInterval));
} else {
this.innerValue.displayValue += this.translate.instant('timewindow.last-prefix') + ' ' +
this.millisecondsToTimeStringPipe.transform(this.innerValue.realtime.timewindowMs);
}
} else {
this.innerValue.displayValue = !this.historyOnly ? (this.translate.instant('timewindow.history') + ' - ') : '';
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');

57
ui-ngx/src/app/shared/components/time/timezone-select.component.ts

@ -16,7 +16,7 @@
import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { map, mergeMap, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
@ -43,10 +43,6 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
defaultTimezoneId: string = null;
timezones$ = getTimezones().pipe(
share()
);
@Input()
set defaultTimezone(timezone: string) {
if (this.defaultTimezoneId !== timezone) {
@ -138,23 +134,20 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
writeValue(value: string | null): void {
this.searchText = '';
getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe(
(foundTimezone) => {
if (foundTimezone !== null) {
this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false});
if (foundTimezone.id !== value) {
setTimeout(() => {
this.updateView(foundTimezone.id);
}, 0);
} else {
this.modelValue = value;
}
} else {
this.modelValue = null;
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
}
const foundTimezone = getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue);
if (foundTimezone !== null) {
this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false});
if (foundTimezone.id !== value) {
setTimeout(() => {
this.updateView(foundTimezone.id);
}, 0);
} else {
this.modelValue = value;
}
);
} else {
this.modelValue = null;
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
}
this.dirty = true;
}
@ -170,14 +163,12 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
this.ignoreClosePanel = false;
} else {
if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) {
getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe(
(defaultTimezoneInfo) => {
if (defaultTimezoneInfo !== null) {
this.ngZone.run(() => {
this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true});
});
}
});
const defaultTimezoneInfo = getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue);
if (defaultTimezoneInfo !== null) {
this.ngZone.run(() => {
this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true});
});
}
}
}
}
@ -196,12 +187,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> {
this.searchText = searchText;
if (searchText && searchText.length) {
return getTimezones().pipe(
map((timezones) => timezones.filter((timezoneInfo) =>
timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())))
);
return of(getTimezones().filter((timezoneInfo) =>
timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())));
}
return getTimezones();
return of(getTimezones());
}
clear() {

41
ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts

@ -430,6 +430,38 @@ export class EntityDataUpdate extends DataUpdate<EntityData> {
constructor(msg: EntityDataUpdateMsg) {
super(msg);
}
public prepareData(tsOffset: number) {
if (this.data) {
this.processEntityData(this.data.data, tsOffset);
}
if (this.update) {
this.processEntityData(this.update, tsOffset);
}
}
private processEntityData(data: Array<EntityData>, tsOffset: number) {
for (const entityData of data) {
if (entityData.timeseries) {
for (const key of Object.keys(entityData.timeseries)) {
const tsValues = entityData.timeseries[key];
for (const tsValue of tsValues) {
tsValue.ts += tsOffset;
}
}
}
if (entityData.latest) {
for (const entityKeyType of Object.keys(entityData.latest)) {
const keyTypeValues = entityData.latest[entityKeyType];
for (const key of Object.keys(keyTypeValues)) {
const tsValue = keyTypeValues[key];
tsValue.ts += tsOffset;
}
}
}
}
}
}
export class AlarmDataUpdate extends DataUpdate<AlarmData> {
@ -468,6 +500,8 @@ export class TelemetrySubscriber {
private zone: NgZone;
private tsOffset = 0;
public subscriptionCommands: Array<WebsocketCmd>;
public data$ = this.dataSubject.asObservable();
@ -522,6 +556,10 @@ export class TelemetrySubscriber {
this.reconnectSubject.complete();
}
public setTsOffset(tsOffset: number) {
this.tsOffset = tsOffset;
}
public onData(message: SubscriptionUpdate) {
const cmdId = message.subscriptionId;
let keys: string[];
@ -545,6 +583,9 @@ export class TelemetrySubscriber {
}
public onEntityData(message: EntityDataUpdate) {
if (this.tsOffset) {
message.prepareData(this.tsOffset);
}
if (this.zone) {
this.zone.run(
() => {

377
ui-ngx/src/app/shared/models/time/time.models.ts

@ -17,9 +17,7 @@
import { TimeService } from '@core/services/time.service';
import { deepClone, isDefined, isUndefined } from '@app/core/utils';
import * as moment_ from 'moment';
import { Observable } from 'rxjs/internal/Observable';
import { from, of } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
import * as monentTz from 'moment-timezone';
const moment = moment_;
@ -27,6 +25,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 {
@ -34,14 +33,25 @@ export enum TimewindowType {
HISTORY
}
export enum RealtimeWindowType {
LAST_INTERVAL,
INTERVAL
}
export enum HistoryWindowType {
LAST_INTERVAL,
FIXED
FIXED,
INTERVAL
}
export interface IntervalWindow {
interval?: number;
timewindowMs?: number;
quickInterval?: QuickTimeInterval;
}
export interface RealtimeWindow extends IntervalWindow{
realtimeType?: RealtimeWindowType;
}
export interface FixedWindow {
@ -85,10 +95,12 @@ export interface Timewindow {
hideInterval?: boolean;
hideAggregation?: boolean;
hideAggInterval?: boolean;
hideTimezone?: boolean;
selectedTab?: TimewindowType;
realtime?: IntervalWindow;
realtime?: RealtimeWindow;
history?: HistoryWindow;
aggregation?: Aggregation;
timezone?: string;
}
export interface SubscriptionAggregation extends Aggregation {
@ -99,6 +111,9 @@ export interface SubscriptionAggregation extends Aggregation {
export interface SubscriptionTimewindow {
startTs?: number;
quickInterval?: QuickTimeInterval;
timezone?: string;
tsOffset?: number;
realtimeWindowMs?: number;
fixedWindow?: FixedWindow;
aggregation?: SubscriptionAggregation;
@ -108,9 +123,46 @@ export interface WidgetTimewindow {
minTime?: number;
maxTime?: number;
interval?: number;
timezone?: string;
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',
CURRENT_HOUR = 'CURRENT_HOUR',
CURRENT_DAY = 'CURRENT_DAY',
CURRENT_DAY_SO_FAR = 'CURRENT_DAY_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.CURRENT_HOUR, 'timeinterval.predefined.current-hour'],
[QuickTimeInterval.CURRENT_DAY, 'timeinterval.predefined.current-day'],
[QuickTimeInterval.CURRENT_DAY_SO_FAR, 'timeinterval.predefined.current-day-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,
@ -129,10 +181,13 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
hideInterval: false,
hideAggregation: false,
hideAggInterval: false,
hideTimezone: false,
selectedTab: TimewindowType.REALTIME,
realtime: {
realtimeType: RealtimeWindowType.LAST_INTERVAL,
interval: SECOND,
timewindowMs: MINUTE
timewindowMs: MINUTE,
quickInterval: QuickTimeInterval.CURRENT_DAY
},
history: {
historyType: HistoryWindowType.LAST_INTERVAL,
@ -141,7 +196,8 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
fixedTimewindow: {
startTimeMs: currentTime - DAY,
endTimeMs: currentTime
}
},
quickInterval: QuickTimeInterval.CURRENT_DAY
},
aggregation: {
type: AggregationType.AVG,
@ -157,6 +213,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
model.hideInterval = value.hideInterval;
model.hideAggregation = value.hideAggregation;
model.hideAggInterval = value.hideAggInterval;
model.hideTimezone = value.hideTimezone;
if (isUndefined(value.selectedTab)) {
if (value.realtime) {
model.selectedTab = TimewindowType.REALTIME;
@ -170,7 +227,20 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
if (isDefined(value.realtime.interval)) {
model.realtime.interval = value.realtime.interval;
}
model.realtime.timewindowMs = value.realtime.timewindowMs;
if (isUndefined(value.realtime.realtimeType)) {
if (isDefined(value.realtime.quickInterval)) {
model.realtime.realtimeType = RealtimeWindowType.INTERVAL;
} else {
model.realtime.realtimeType = RealtimeWindowType.LAST_INTERVAL;
}
} else {
model.realtime.realtimeType = value.realtime.realtimeType;
}
if (model.realtime.realtimeType === RealtimeWindowType.INTERVAL) {
model.realtime.quickInterval = value.realtime.quickInterval;
} else {
model.realtime.timewindowMs = value.realtime.timewindowMs;
}
} else {
if (isDefined(value.history.interval)) {
model.history.interval = value.history.interval;
@ -178,6 +248,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 +258,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;
@ -197,6 +271,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
}
model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2);
}
model.timezone = value.timezone;
}
return model;
}
@ -223,6 +298,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
hideInterval: timewindow.hideInterval || false,
hideAggregation: timewindow.hideAggregation || false,
hideAggInterval: timewindow.hideAggInterval || false,
hideTimezone: timewindow.hideTimezone || false,
selectedTab: TimewindowType.HISTORY,
history: {
historyType: HistoryWindowType.FIXED,
@ -235,7 +311,8 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
aggregation: {
type: aggType,
limit
}
},
timezone: timewindow.timezone
};
return historyTimewindow;
}
@ -249,8 +326,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
interval: SECOND,
limit: timeService.getMaxDatapointsLimit(),
type: AggregationType.AVG
}
},
timezone: timewindow.timezone,
tsOffset: 0
};
if (timewindow.timezone) {
const tz = getTimezone(timewindow.timezone);
const localOffset = moment().utcOffset();
subscriptionTimewindow.tsOffset = (tz.utcOffset() - localOffset) * 60 * 1000;
}
let aggTimewindow = 0;
if (stateData) {
subscriptionTimewindow.aggregation.type = AggregationType.NONE;
@ -267,33 +351,65 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
selectedTab = isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY;
}
if (selectedTab === TimewindowType.REALTIME) {
subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs;
let realtimeType = timewindow.realtime.realtimeType;
if (isUndefined(realtimeType)) {
if (isDefined(timewindow.realtime.quickInterval)) {
realtimeType = RealtimeWindowType.INTERVAL;
} else {
realtimeType = RealtimeWindowType.LAST_INTERVAL;
}
}
if (realtimeType === RealtimeWindowType.INTERVAL) {
const currentDate = getCurrentTime(timewindow.timezone);
subscriptionTimewindow.realtimeWindowMs =
getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, currentDate);
subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval;
subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate);
} else {
subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs;
subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs;
}
subscriptionTimewindow.aggregation.interval =
timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval,
subscriptionTimewindow.aggregation.type);
subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs;
const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval;
aggTimewindow = subscriptionTimewindow.realtimeWindowMs;
if (startDiff) {
subscriptionTimewindow.startTs -= startDiff;
aggTimewindow += subscriptionTimewindow.aggregation.interval;
if (realtimeType !== RealtimeWindowType.INTERVAL) {
const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval;
if (startDiff) {
subscriptionTimewindow.startTs -= startDiff;
aggTimewindow += subscriptionTimewindow.aggregation.interval;
}
}
} 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();
const currentDate = getCurrentTime(timewindow.timezone);
const currentTime = currentDate.valueOf();
subscriptionTimewindow.fixedWindow = {
startTimeMs: currentTime - timewindow.history.timewindowMs,
endTimeMs: currentTime
};
aggTimewindow = timewindow.history.timewindowMs;
} else if (historyType === HistoryWindowType.INTERVAL) {
const currentDate = getCurrentTime(timewindow.timezone);
subscriptionTimewindow.fixedWindow = {
startTimeMs: calculateIntervalStartTime(timewindow.history.quickInterval, currentDate),
endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate)
};
aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
} else {
subscriptionTimewindow.fixedWindow = {
startTimeMs: timewindow.history.fixedTimewindow.startTimeMs,
endTimeMs: timewindow.history.fixedTimewindow.endTimeMs
startTimeMs: timewindow.history.fixedTimewindow.startTimeMs - subscriptionTimewindow.tsOffset,
endTimeMs: timewindow.history.fixedTimewindow.endTimeMs - subscriptionTimewindow.tsOffset
};
aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
}
@ -309,6 +425,127 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
return subscriptionTimewindow;
}
function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval, currentDate: moment_.Moment): number {
switch (interval) {
case QuickTimeInterval.CURRENT_HOUR:
return HOUR;
case QuickTimeInterval.CURRENT_DAY:
case QuickTimeInterval.CURRENT_DAY_SO_FAR:
return DAY;
case QuickTimeInterval.CURRENT_WEEK:
case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
return WEEK;
case QuickTimeInterval.CURRENT_MONTH:
case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
return currentDate.endOf('month').diff(currentDate.clone().startOf('month'));
case QuickTimeInterval.CURRENT_YEAR:
case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
return currentDate.endOf('year').diff(currentDate.clone().startOf('year'));
}
}
export function calculateIntervalEndTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {
currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz);
switch (interval) {
case QuickTimeInterval.YESTERDAY:
currentDate.subtract(1, 'days');
return currentDate.endOf('day').valueOf();
case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
currentDate.subtract(2, 'days');
return currentDate.endOf('day').valueOf();
case QuickTimeInterval.THIS_DAY_LAST_WEEK:
currentDate.subtract(1, 'weeks');
return currentDate.endOf('day').valueOf();
case QuickTimeInterval.PREVIOUS_WEEK:
currentDate.subtract(1, 'weeks');
return currentDate.endOf('week').valueOf();
case QuickTimeInterval.PREVIOUS_MONTH:
currentDate.subtract(1, 'months');
return currentDate.endOf('month').valueOf();
case QuickTimeInterval.PREVIOUS_YEAR:
currentDate.subtract(1, 'years');
return currentDate.endOf('year').valueOf();
case QuickTimeInterval.CURRENT_HOUR:
return currentDate.endOf('hour').valueOf();
case QuickTimeInterval.CURRENT_DAY:
return currentDate.endOf('day').valueOf();
case QuickTimeInterval.CURRENT_WEEK:
return currentDate.endOf('week').valueOf();
case QuickTimeInterval.CURRENT_MONTH:
return currentDate.endOf('month').valueOf();
case QuickTimeInterval.CURRENT_YEAR:
return currentDate.endOf('year').valueOf();
case QuickTimeInterval.CURRENT_DAY_SO_FAR:
case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
return currentDate.valueOf();
}
}
export function calculateIntervalStartTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {
currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz);
switch (interval) {
case QuickTimeInterval.YESTERDAY:
currentDate.subtract(1, 'days');
return currentDate.startOf('day').valueOf();
case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
currentDate.subtract(2, '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:
currentDate.subtract(1, 'weeks');
return currentDate.startOf('week').valueOf();
case QuickTimeInterval.PREVIOUS_MONTH:
currentDate.subtract(1, 'months');
return currentDate.startOf('month').valueOf();
case QuickTimeInterval.PREVIOUS_YEAR:
currentDate.subtract(1, 'years');
return currentDate.startOf('year').valueOf();
case QuickTimeInterval.CURRENT_HOUR:
return currentDate.startOf('hour').valueOf();
case QuickTimeInterval.CURRENT_DAY:
case QuickTimeInterval.CURRENT_DAY_SO_FAR:
return currentDate.startOf('day').valueOf();
case QuickTimeInterval.CURRENT_WEEK:
case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
return currentDate.startOf('week').valueOf();
case QuickTimeInterval.CURRENT_MONTH:
case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
return currentDate.startOf('month').valueOf();
case QuickTimeInterval.CURRENT_YEAR:
case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
return currentDate.startOf('year').valueOf();
}
}
export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number {
switch (interval) {
case QuickTimeInterval.CURRENT_HOUR:
return HOUR;
case QuickTimeInterval.YESTERDAY:
case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
case QuickTimeInterval.THIS_DAY_LAST_WEEK:
case QuickTimeInterval.CURRENT_DAY:
case QuickTimeInterval.CURRENT_DAY_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 = {
@ -339,6 +576,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
cloned.hideInterval = timewindow.hideInterval || false;
cloned.hideAggregation = timewindow.hideAggregation || false;
cloned.hideAggInterval = timewindow.hideAggInterval || false;
cloned.hideTimezone = timewindow.hideTimezone || false;
if (isDefined(timewindow.selectedTab)) {
cloned.selectedTab = timewindow.selectedTab;
if (timewindow.selectedTab === TimewindowType.REALTIME) {
@ -348,6 +586,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
}
}
cloned.aggregation = deepClone(timewindow.aggregation);
cloned.timezone = timewindow.timezone;
return cloned;
}
@ -358,6 +597,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 +616,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 +641,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 +671,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 +696,7 @@ export const defaultTimeIntervals = new Array<TimeInterval>(
{
name: 'timeinterval.days-interval',
translateParams: {days: 1},
value: 1 * DAY
value: DAY
},
{
name: 'timeinterval.days-interval',
@ -495,60 +736,50 @@ export interface TimezoneInfo {
let timezones: TimezoneInfo[] = null;
let defaultTimezone: string = null;
export function getTimezones(): Observable<TimezoneInfo[]> {
if (timezones) {
return of(timezones);
} else {
return from(import('moment-timezone')).pipe(
map((monentTz) => {
return monentTz.tz.names().map((zoneName) => {
const tz = monentTz.tz(zoneName);
return {
id: zoneName,
name: zoneName.replace(/_/g, ' '),
offset: `UTC${tz.format('Z')}`,
nOffset: tz.utcOffset()
};
});
}),
tap((zones) => {
timezones = zones;
})
);
export function getTimezones(): TimezoneInfo[] {
if (!timezones) {
timezones = monentTz.tz.names().map((zoneName) => {
const tz = monentTz.tz(zoneName);
return {
id: zoneName,
name: zoneName.replace(/_/g, ' '),
offset: `UTC${tz.format('Z')}`,
nOffset: tz.utcOffset()
};
});
}
return timezones;
}
export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): Observable<TimezoneInfo> {
return getTimezones().pipe(
mergeMap((timezoneList) => {
let foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId);
if (!foundTimezone) {
if (userTimezoneByDefault) {
return getDefaultTimezone().pipe(
map((userTimezone) => {
return timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone);
})
);
} else if (defaultTimezoneId) {
foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId);
}
}
return of(foundTimezone);
})
);
export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): TimezoneInfo {
const timezoneList = getTimezones();
let foundTimezone = timezoneId ? timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId) : null;
if (!foundTimezone) {
if (userTimezoneByDefault) {
const userTimezone = getDefaultTimezone();
foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone);
} else if (defaultTimezoneId) {
foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId);
}
}
return foundTimezone;
}
export function getDefaultTimezone(): Observable<string> {
if (defaultTimezone) {
return of(defaultTimezone);
export function getDefaultTimezone(): string {
if (!defaultTimezone) {
defaultTimezone = monentTz.tz.guess();
}
return defaultTimezone;
}
export function getCurrentTime(tz?: string): moment_.Moment {
if (tz) {
return moment().tz(tz);
} else {
return from(import('moment-timezone')).pipe(
map((monentTz) => {
return monentTz.tz.guess();
}),
tap((zone) => {
defaultTimezone = zone;
})
);
return moment();
}
}
export function getTimezone(tz: string): moment_.Moment {
return moment.tz(tz);
}

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,

22
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2118,7 +2118,24 @@
"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",
"current-hour": "Current hour",
"current-day": "Current day",
"current-day-so-far": "Current day 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",
@ -2139,7 +2156,8 @@
"date-range": "Date range",
"last": "Last",
"time-period": "Time period",
"hide": "Hide"
"hide": "Hide",
"interval": "Interval"
},
"user": {
"user": "User",

Loading…
Cancel
Save