Browse Source

Improve timezone offset handling for latest values and alarm widgets. Improve timezone selector - add local browser time info on empty value.

pull/4266/head
Igor Kulikov 6 years ago
parent
commit
a17c5b7757
  1. 6
      ui-ngx/src/app/core/api/alarm-data-subscription.ts
  2. 66
      ui-ngx/src/app/core/api/entity-data-subscription.ts
  3. 5
      ui-ngx/src/app/core/api/entity-data.service.ts
  4. 40
      ui-ngx/src/app/core/api/widget-subscription.ts
  5. 5
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.html
  6. 59
      ui-ngx/src/app/shared/components/time/timezone-select.component.ts
  7. 72
      ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts
  8. 23
      ui-ngx/src/app/shared/models/time/time.models.ts
  9. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
ui-ngx/src/app/core/api/alarm-data-subscription.ts

@ -130,6 +130,7 @@ export class AlarmDataSubscription {
this.alarmDataCommand.query.pageLink.timeWindow = this.subsTw.realtimeWindowMs; this.alarmDataCommand.query.pageLink.timeWindow = this.subsTw.realtimeWindowMs;
} }
this.subscriber.setTsOffset(this.subsTw.tsOffset);
this.subscriber.subscriptionCommands.push(this.alarmDataCommand); this.subscriber.subscriptionCommands.push(this.alarmDataCommand);
this.subscriber.alarmData$.subscribe((alarmDataUpdate) => { this.subscriber.alarmData$.subscribe((alarmDataUpdate) => {
@ -143,8 +144,11 @@ export class AlarmDataSubscription {
this.subscriber.subscribe(); this.subscriber.subscribe();
} else if (this.datasourceType === DatasourceType.function) { } else if (this.datasourceType === DatasourceType.function) {
const alarm = deepClone(simulatedAlarm);
alarm.createdTime += this.subsTw.tsOffset;
alarm.startTs += this.subsTw.tsOffset;
const pageData: PageData<AlarmData> = { const pageData: PageData<AlarmData> = {
data: [{...simulatedAlarm, entityId: '1', latest: {}}], data: [{...alarm, entityId: '1', latest: {}}],
hasNext: false, hasNext: false,
totalElements: 1, totalElements: 1,
totalPages: 1 totalPages: 1

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

@ -15,7 +15,7 @@
/// ///
import { DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models'; import { DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models';
import { AggregationType, SubscriptionTimewindow } from '@shared/models/time/time.models'; import { AggregationType, getCurrentTime, SubscriptionTimewindow } from '@shared/models/time/time.models';
import { import {
EntityData, EntityData,
EntityDataPageLink, EntityDataPageLink,
@ -74,6 +74,7 @@ export interface EntityDataSubscriptionOptions {
keyFilters?: Array<KeyFilter>; keyFilters?: Array<KeyFilter>;
additionalKeyFilters?: Array<KeyFilter>; additionalKeyFilters?: Array<KeyFilter>;
subscriptionTimewindow?: SubscriptionTimewindow; subscriptionTimewindow?: SubscriptionTimewindow;
latestTsOffset?: number;
} }
export class EntityDataSubscription { export class EntityDataSubscription {
@ -95,6 +96,7 @@ export class EntityDataSubscription {
private entityDataResolveSubject: Subject<EntityDataLoadResult>; private entityDataResolveSubject: Subject<EntityDataLoadResult>;
private pageData: PageData<EntityData>; private pageData: PageData<EntityData>;
private subsTw: SubscriptionTimewindow; private subsTw: SubscriptionTimewindow;
private latestTsOffset: number;
private dataAggregators: Array<DataAggregator>; private dataAggregators: Array<DataAggregator>;
private dataKeys: {[key: string]: Array<SubscriptionDataKey> | SubscriptionDataKey} = {}; private dataKeys: {[key: string]: Array<SubscriptionDataKey> | SubscriptionDataKey} = {};
private datasourceData: {[index: number]: {[key: string]: DataSetHolder}}; private datasourceData: {[index: number]: {[key: string]: DataSetHolder}};
@ -177,6 +179,7 @@ export class EntityDataSubscription {
this.started = true; this.started = true;
this.dataResolved = true; this.dataResolved = true;
this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow;
this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset;
this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow &&
isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow);
this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow &&
@ -237,7 +240,12 @@ export class EntityDataSubscription {
}; };
if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
this.prepareSubscriptionCommands(); this.prepareSubscriptionCommands(this.dataCommand);
if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
this.subscriber.setTsOffset(this.subsTw.tsOffset);
} else {
this.subscriber.setTsOffset(this.latestTsOffset);
}
} }
this.subscriber.subscriptionCommands.push(this.dataCommand); this.subscriber.subscriptionCommands.push(this.dataCommand);
@ -276,9 +284,15 @@ export class EntityDataSubscription {
this.subscriber.subscriptionCommands = [this.dataCommand]; this.subscriber.subscriptionCommands = [this.dataCommand];
} }
}); });
this.subscriber.subscribe(); this.subscriber.subscribe();
} else if (this.datasourceType === DatasourceType.function) { } else if (this.datasourceType === DatasourceType.function) {
let tsOffset = 0;
if (this.entityDataSubscriptionOptions.type === widgetType.latest) {
tsOffset = this.entityDataSubscriptionOptions.latestTsOffset;
} else if (this.entityDataSubscriptionOptions.subscriptionTimewindow) {
tsOffset = this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
}
const entityData: EntityData = { const entityData: EntityData = {
entityId: { entityId: {
id: NULL_UUID, id: NULL_UUID,
@ -289,7 +303,7 @@ export class EntityDataSubscription {
}; };
const name = DatasourceType.function; const name = DatasourceType.function;
entityData.latest[EntityKeyType.ENTITY_FIELD] = { entityData.latest[EntityKeyType.ENTITY_FIELD] = {
name: {ts: Date.now(), value: name} name: {ts: Date.now() + tsOffset, value: name}
}; };
const pageData: PageData<EntityData> = { const pageData: PageData<EntityData> = {
data: [entityData], data: [entityData],
@ -299,7 +313,9 @@ export class EntityDataSubscription {
}; };
this.onPageData(pageData); this.onPageData(pageData);
} else if (this.datasourceType === DatasourceType.entityCount) { } else if (this.datasourceType === DatasourceType.entityCount) {
this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset;
this.subscriber = new TelemetrySubscriber(this.telemetryService); this.subscriber = new TelemetrySubscriber(this.telemetryService);
this.subscriber.setTsOffset(this.latestTsOffset);
this.countCommand = new EntityCountCmd(); this.countCommand = new EntityCountCmd();
let keyFilters = this.entityDataSubscriptionOptions.keyFilters; let keyFilters = this.entityDataSubscriptionOptions.keyFilters;
if (this.entityDataSubscriptionOptions.additionalKeyFilters) { if (this.entityDataSubscriptionOptions.additionalKeyFilters) {
@ -332,13 +348,13 @@ export class EntityDataSubscription {
latest: { latest: {
[EntityKeyType.ENTITY_FIELD]: { [EntityKeyType.ENTITY_FIELD]: {
name: { name: {
ts: Date.now(), ts: Date.now() + this.latestTsOffset,
value: DatasourceType.entityCount value: DatasourceType.entityCount
} }
}, },
[EntityKeyType.COUNT]: { [EntityKeyType.COUNT]: {
[countKey.name]: { [countKey.name]: {
ts: Date.now(), ts: Date.now() + this.latestTsOffset,
value: entityCountUpdate.count + '' value: entityCountUpdate.count + ''
} }
} }
@ -359,7 +375,7 @@ export class EntityDataSubscription {
latest: { latest: {
[EntityKeyType.COUNT]: { [EntityKeyType.COUNT]: {
[countKey.name]: { [countKey.name]: {
ts: Date.now(), ts: Date.now() + this.latestTsOffset,
value: entityCountUpdate.count + '' value: entityCountUpdate.count + ''
} }
} }
@ -384,6 +400,7 @@ export class EntityDataSubscription {
return; return;
} }
this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow;
this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset;
this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow &&
isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow);
this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow &&
@ -394,22 +411,38 @@ export class EntityDataSubscription {
if (this.datasourceType === DatasourceType.entity) { if (this.datasourceType === DatasourceType.entity) {
this.subsCommand = new EntityDataCmd(); this.subsCommand = new EntityDataCmd();
this.subsCommand.cmdId = this.dataCommand.cmdId; this.subsCommand.cmdId = this.dataCommand.cmdId;
this.prepareSubscriptionCommands(); this.prepareSubscriptionCommands(this.subsCommand);
if (!this.subsCommand.isEmpty()) { let latestTsOffsetChanged = false;
if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
this.subscriber.setTsOffset(this.subsTw.tsOffset);
} else {
latestTsOffsetChanged = this.subscriber.setTsOffset(this.latestTsOffset);
}
if (latestTsOffsetChanged) {
if (this.listener.initialPageDataChanged) {
this.listener.initialPageDataChanged(this.pageData);
}
} else if (!this.subsCommand.isEmpty()) {
this.subscriber.subscriptionCommands = [this.subsCommand]; this.subscriber.subscriptionCommands = [this.subsCommand];
this.subscriber.update(); this.subscriber.update();
} }
} else if (this.datasourceType === DatasourceType.entityCount) {
if (this.subscriber.setTsOffset(this.latestTsOffset)) {
if (this.listener.initialPageDataChanged) {
this.listener.initialPageDataChanged(this.pageData);
}
}
} else if (this.datasourceType === DatasourceType.function) { } else if (this.datasourceType === DatasourceType.function) {
this.startFunction(); this.startFunction();
} }
this.started = true; this.started = true;
} }
private prepareSubscriptionCommands() { private prepareSubscriptionCommands(cmd: EntityDataCmd) {
if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
if (this.tsFields.length > 0) { if (this.tsFields.length > 0) {
if (this.history) { if (this.history) {
this.subsCommand.historyCmd = { cmd.historyCmd = {
keys: this.tsFields.map(key => key.key), keys: this.tsFields.map(key => key.key),
startTs: this.subsTw.fixedWindow.startTimeMs, startTs: this.subsTw.fixedWindow.startTimeMs,
endTs: this.subsTw.fixedWindow.endTimeMs, endTs: this.subsTw.fixedWindow.endTimeMs,
@ -419,7 +452,7 @@ export class EntityDataSubscription {
fetchLatestPreviousPoint: this.subsTw.aggregation.stateData fetchLatestPreviousPoint: this.subsTw.aggregation.stateData
}; };
} else { } else {
this.subsCommand.tsCmd = { cmd.tsCmd = {
keys: this.tsFields.map(key => key.key), keys: this.tsFields.map(key => key.key),
startTs: this.subsTw.startTs, startTs: this.subsTw.startTs,
timeWindow: this.subsTw.aggregation.timeWindow, timeWindow: this.subsTw.aggregation.timeWindow,
@ -430,10 +463,9 @@ export class EntityDataSubscription {
}; };
} }
} }
this.subscriber.setTsOffset(this.subsTw.tsOffset);
} else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) {
if (this.latestValues.length > 0) { if (this.latestValues.length > 0) {
this.subsCommand.latestCmd = { cmd.latestCmd = {
keys: this.latestValues keys: this.latestValues
}; };
} }
@ -783,7 +815,7 @@ export class EntityDataSubscription {
} else { } else {
prevSeries = [0, 0]; prevSeries = [0, 0];
} }
const time = Date.now(); const time = Date.now() + this.latestTsOffset;
const value = dataKey.func(time, prevSeries[1]); const value = dataKey.func(time, prevSeries[1]);
const series: [number, any] = [time, value]; const series: [number, any] = [time, value];
this.datasourceData[0][dataKey.key].data = [series]; this.datasourceData[0][dataKey.key].data = [series];
@ -838,6 +870,10 @@ export class EntityDataSubscription {
endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs + endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs +
this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
} }
if (this.entityDataSubscriptionOptions.subscriptionTimewindow.quickInterval) {
const currentTime = getCurrentTime().valueOf() + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
endTime = Math.min(currentTime, endTime);
}
} }
generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime); generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime);
} }

5
ui-ngx/src/app/core/api/entity-data.service.ts

@ -32,6 +32,7 @@ import { Observable, of } from 'rxjs';
export interface EntityDataListener { export interface EntityDataListener {
subscriptionType: widgetType; subscriptionType: widgetType;
subscriptionTimewindow?: SubscriptionTimewindow; subscriptionTimewindow?: SubscriptionTimewindow;
latestTsOffset?: number;
configDatasource: Datasource; configDatasource: Datasource;
configDatasourceIndex: number; configDatasourceIndex: number;
dataLoaded: (pageData: PageData<EntityData>, dataLoaded: (pageData: PageData<EntityData>,
@ -92,6 +93,8 @@ export class EntityDataService {
if (listener.subscription) { if (listener.subscription) {
if (listener.subscriptionType === widgetType.timeseries) { if (listener.subscriptionType === widgetType.timeseries) {
listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow);
} else if (listener.subscriptionType === widgetType.latest) {
listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset;
} }
listener.subscription.start(); listener.subscription.start();
} }
@ -118,6 +121,8 @@ export class EntityDataService {
listener.subscription = new EntityDataSubscription(listener, this.telemetryService, this.utils); listener.subscription = new EntityDataSubscription(listener, this.telemetryService, this.utils);
if (listener.subscriptionType === widgetType.timeseries) { if (listener.subscriptionType === widgetType.timeseries) {
listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow);
} else if (listener.subscriptionType === widgetType.latest) {
listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset;
} }
return listener.subscription.subscribe(); return listener.subscription.subscribe();
} }

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

@ -39,8 +39,10 @@ import { HttpErrorResponse } from '@angular/common/http';
import { import {
calculateIntervalEndTime, calculateIntervalEndTime,
calculateIntervalStartTime, calculateIntervalStartTime,
calculateTsOffset,
createSubscriptionTimewindow, createSubscriptionTimewindow,
createTimewindowForComparison, getCurrentTime, createTimewindowForComparison,
getCurrentTime,
SubscriptionTimewindow, SubscriptionTimewindow,
Timewindow, Timewindow,
toHistoryTimewindow, toHistoryTimewindow,
@ -79,8 +81,10 @@ export class WidgetSubscription implements IWidgetSubscription {
timeWindow: WidgetTimewindow; timeWindow: WidgetTimewindow;
originalTimewindow: Timewindow; originalTimewindow: Timewindow;
timeWindowConfig: Timewindow; timeWindowConfig: Timewindow;
timezone: string;
subscriptionTimewindow: SubscriptionTimewindow; subscriptionTimewindow: SubscriptionTimewindow;
useDashboardTimewindow: boolean; useDashboardTimewindow: boolean;
tsOffset = 0;
hasDataPageLink: boolean; hasDataPageLink: boolean;
singleEntity: boolean; singleEntity: boolean;
@ -213,6 +217,10 @@ export class WidgetSubscription implements IWidgetSubscription {
this.timeWindow = {}; this.timeWindow = {};
this.useDashboardTimewindow = options.useDashboardTimewindow; this.useDashboardTimewindow = options.useDashboardTimewindow;
this.stateData = options.stateData; this.stateData = options.stateData;
if (this.type === widgetType.latest) {
this.timezone = options.dashboardTimewindow.timezone;
this.updateTsOffset();
}
if (this.useDashboardTimewindow) { if (this.useDashboardTimewindow) {
this.timeWindowConfig = deepClone(options.dashboardTimewindow); this.timeWindowConfig = deepClone(options.dashboardTimewindow);
} else { } else {
@ -578,11 +586,16 @@ export class WidgetSubscription implements IWidgetSubscription {
if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
this.timeWindowConfig = deepClone(newDashboardTimewindow); this.timeWindowConfig = deepClone(newDashboardTimewindow);
this.update(); this.update();
return true; }
}
} else if (this.type === widgetType.latest) {
if (newDashboardTimewindow && this.timezone !== newDashboardTimewindow.timezone) {
this.timezone = newDashboardTimewindow.timezone;
if (this.updateTsOffset()) {
this.update();
} }
} }
} }
return false;
} }
updateDataVisibility(index: number): void { updateDataVisibility(index: number): void {
@ -815,6 +828,7 @@ export class WidgetSubscription implements IWidgetSubscription {
configDatasource: datasource, configDatasource: datasource,
configDatasourceIndex: datasourceIndex, configDatasourceIndex: datasourceIndex,
subscriptionTimewindow: this.subscriptionTimewindow, subscriptionTimewindow: this.subscriptionTimewindow,
latestTsOffset: this.tsOffset,
dataLoaded: (pageData, data1, datasourceIndex1, pageLink1) => { dataLoaded: (pageData, data1, datasourceIndex1, pageLink1) => {
this.dataLoaded(pageData, data1, datasourceIndex1, pageLink1, true); this.dataLoaded(pageData, data1, datasourceIndex1, pageLink1, true);
}, },
@ -882,25 +896,28 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
private dataSubscribe() { private dataSubscribe() {
this.updateDataTimewindow();
if (!this.hasDataPageLink) { if (!this.hasDataPageLink) {
if (this.type === widgetType.timeseries && this.timeWindowConfig) { if (this.type === widgetType.timeseries && this.timeWindowConfig && this.subscriptionTimewindow.fixedWindow) {
this.updateDataTimewindow();
if (this.subscriptionTimewindow.fixedWindow) {
this.onDataUpdated(); this.onDataUpdated();
}
} }
const forceUpdate = !this.datasources.length; const forceUpdate = !this.datasources.length;
const notifyDataLoaded = !this.entityDataListeners.filter((listener) => listener.subscription ? true : false).length;
this.entityDataListeners.forEach((listener) => { this.entityDataListeners.forEach((listener) => {
if (this.comparisonEnabled && listener.configDatasource.isAdditional) { if (this.comparisonEnabled && listener.configDatasource.isAdditional) {
listener.subscriptionTimewindow = this.timewindowForComparison; listener.subscriptionTimewindow = this.timewindowForComparison;
} else { } else {
listener.subscriptionTimewindow = this.subscriptionTimewindow; listener.subscriptionTimewindow = this.subscriptionTimewindow;
listener.latestTsOffset = this.tsOffset;
} }
this.ctx.entityDataService.startSubscription(listener); this.ctx.entityDataService.startSubscription(listener);
}); });
if (forceUpdate) { if (forceUpdate) {
this.onDataUpdated(); this.onDataUpdated();
} }
if (notifyDataLoaded) {
this.notifyDataLoaded();
}
} }
} }
@ -1102,6 +1119,15 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
} }
private updateTsOffset(): boolean {
const newOffset = calculateTsOffset(this.timezone);
if (this.tsOffset !== newOffset) {
this.tsOffset = newOffset;
return true;
}
return false;
}
private updateRealtimeSubscription(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow { private updateRealtimeSubscription(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow {
if (subscriptionTimewindow) { if (subscriptionTimewindow) {
this.subscriptionTimewindow = subscriptionTimewindow; this.subscriptionTimewindow = subscriptionTimewindow;

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

@ -133,7 +133,7 @@
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggInterval" <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggInterval"
(ngModelChange)="onHideAggIntervalChanged()"></mat-checkbox> (ngModelChange)="onHideAggIntervalChanged()"></mat-checkbox>
</section> </section>
<section fxLayout="column" [fxShow]="isEdit || !timewindow.hideAggInterval"> <section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideAggInterval">
<div class="limit-slider-container" <div class="limit-slider-container"
fxLayout="row" fxLayoutAlign="start center"> fxLayout="row" fxLayoutAlign="start center">
<span translate>aggregation.limit</span> <span translate>aggregation.limit</span>
@ -184,7 +184,8 @@
(ngModelChange)="onHideTimezoneChanged()"></mat-checkbox> (ngModelChange)="onHideTimezoneChanged()"></mat-checkbox>
</section> </section>
<tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone" <tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone"
formControlName="timezone"> localBrowserTimezonePlaceholderOnEmpty="true"
formControlName="timezone">
</tb-timezone-select> </tb-timezone-select>
</div> </div>
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">

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

@ -23,7 +23,8 @@ import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { getTimezoneInfo, getTimezones, TimezoneInfo } from '@shared/models/time/time.models'; import { getDefaultTimezoneInfo, getTimezoneInfo, getTimezones, TimezoneInfo } from '@shared/models/time/time.models';
import { deepClone } from '@core/utils';
@Component({ @Component({
selector: 'tb-timezone-select', selector: 'tb-timezone-select',
@ -68,6 +69,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
this.userTimezoneByDefaultValue = coerceBooleanProperty(value); this.userTimezoneByDefaultValue = coerceBooleanProperty(value);
} }
private localBrowserTimezonePlaceholderOnEmptyValue: boolean;
get localBrowserTimezonePlaceholderOnEmpty(): boolean {
return this.localBrowserTimezonePlaceholderOnEmptyValue;
}
@Input()
set localBrowserTimezonePlaceholderOnEmpty(value: boolean) {
this.localBrowserTimezonePlaceholderOnEmptyValue = coerceBooleanProperty(value);
}
@Input() @Input()
disabled: boolean; disabled: boolean;
@ -81,6 +91,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
private dirty = false; private dirty = false;
private localBrowserTimezoneInfoPlaceholder: TimezoneInfo;
private timezones: Array<TimezoneInfo>;
private propagateChange = (v: any) => { }; private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
@ -146,7 +160,11 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
} }
} else { } else {
this.modelValue = null; this.modelValue = null;
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false}); if (this.localBrowserTimezonePlaceholderOnEmptyValue) {
this.selectTimezoneFormGroup.get('timezone').patchValue(this.getLocalBrowserTimezoneInfoPlaceholder(), {emitEvent: false});
} else {
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
}
} }
this.dirty = true; this.dirty = true;
} }
@ -162,11 +180,17 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
if (this.ignoreClosePanel) { if (this.ignoreClosePanel) {
this.ignoreClosePanel = false; this.ignoreClosePanel = false;
} else { } else {
if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) { if (!this.modelValue) {
const defaultTimezoneInfo = getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue); if (this.defaultTimezoneId || this.userTimezoneByDefaultValue) {
if (defaultTimezoneInfo !== null) { const defaultTimezoneInfo = getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue);
if (defaultTimezoneInfo !== null) {
this.ngZone.run(() => {
this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true});
});
}
} else if (this.localBrowserTimezonePlaceholderOnEmptyValue) {
this.ngZone.run(() => { this.ngZone.run(() => {
this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true}); this.selectTimezoneFormGroup.get('timezone').reset(this.getLocalBrowserTimezoneInfoPlaceholder(), {emitEvent: true});
}); });
} }
} }
@ -187,10 +211,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> {
this.searchText = searchText; this.searchText = searchText;
if (searchText && searchText.length) { if (searchText && searchText.length) {
return of(getTimezones().filter((timezoneInfo) => return of(this.loadTimezones().filter((timezoneInfo) =>
timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase()))); timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())));
} }
return of(getTimezones()); return of(this.loadTimezones());
} }
clear() { clear() {
@ -200,4 +224,23 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
}, 0); }, 0);
} }
private loadTimezones(): Array<TimezoneInfo> {
if (!this.timezones) {
this.timezones = [];
if (this.localBrowserTimezonePlaceholderOnEmptyValue) {
this.timezones.push(this.getLocalBrowserTimezoneInfoPlaceholder());
}
this.timezones.push(...getTimezones());
}
return this.timezones;
}
private getLocalBrowserTimezoneInfoPlaceholder(): TimezoneInfo {
if (!this.localBrowserTimezoneInfoPlaceholder) {
this.localBrowserTimezoneInfoPlaceholder = deepClone(getDefaultTimezoneInfo());
this.localBrowserTimezoneInfoPlaceholder.id = null;
this.localBrowserTimezoneInfoPlaceholder.name = this.translate.instant('timezone.browser-time');
}
return this.localBrowserTimezoneInfoPlaceholder;
}
} }

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

@ -30,6 +30,9 @@ import {
TsValue TsValue
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { alarmFields } from '@shared/models/alarm.models';
import { entityFields } from '@shared/models/entity.models';
import { isUndefined } from '@core/utils';
export enum DataKeyType { export enum DataKeyType {
timeseries = 'timeseries', timeseries = 'timeseries',
@ -446,7 +449,9 @@ export class EntityDataUpdate extends DataUpdate<EntityData> {
for (const key of Object.keys(entityData.timeseries)) { for (const key of Object.keys(entityData.timeseries)) {
const tsValues = entityData.timeseries[key]; const tsValues = entityData.timeseries[key];
for (const tsValue of tsValues) { for (const tsValue of tsValues) {
tsValue.ts += tsOffset; if (tsValue.ts) {
tsValue.ts += tsOffset;
}
} }
} }
} }
@ -455,13 +460,17 @@ export class EntityDataUpdate extends DataUpdate<EntityData> {
const keyTypeValues = entityData.latest[entityKeyType]; const keyTypeValues = entityData.latest[entityKeyType];
for (const key of Object.keys(keyTypeValues)) { for (const key of Object.keys(keyTypeValues)) {
const tsValue = keyTypeValues[key]; const tsValue = keyTypeValues[key];
tsValue.ts += tsOffset; if (tsValue.ts) {
tsValue.ts += tsOffset;
}
if (key === entityFields.createdTime.keyName && tsValue.value) {
tsValue.value = (Number(tsValue.value) + tsOffset) + '';
}
} }
} }
} }
} }
} }
} }
export class AlarmDataUpdate extends DataUpdate<AlarmData> { export class AlarmDataUpdate extends DataUpdate<AlarmData> {
@ -473,6 +482,48 @@ export class AlarmDataUpdate extends DataUpdate<AlarmData> {
this.allowedEntities = msg.allowedEntities; this.allowedEntities = msg.allowedEntities;
this.totalEntities = msg.totalEntities; this.totalEntities = msg.totalEntities;
} }
public prepareData(tsOffset: number) {
if (this.data) {
this.processAlarmData(this.data.data, tsOffset);
}
if (this.update) {
this.processAlarmData(this.update, tsOffset);
}
}
private processAlarmData(data: Array<AlarmData>, tsOffset: number) {
for (const alarmData of data) {
alarmData.createdTime += tsOffset;
if (alarmData.ackTs) {
alarmData.ackTs += tsOffset;
}
if (alarmData.clearTs) {
alarmData.clearTs += tsOffset;
}
if (alarmData.endTs) {
alarmData.endTs += tsOffset;
}
if (alarmData.latest) {
for (const entityKeyType of Object.keys(alarmData.latest)) {
const keyTypeValues = alarmData.latest[entityKeyType];
for (const key of Object.keys(keyTypeValues)) {
const tsValue = keyTypeValues[key];
if (tsValue.ts) {
tsValue.ts += tsOffset;
}
if (key in [entityFields.createdTime.keyName,
alarmFields.startTime.keyName,
alarmFields.endTime.keyName,
alarmFields.ackTime.keyName,
alarmFields.clearTime.keyName] && tsValue.value) {
tsValue.value = (Number(tsValue.value) + tsOffset) + '';
}
}
}
}
}
}
} }
export class EntityCountUpdate extends CmdUpdate { export class EntityCountUpdate extends CmdUpdate {
@ -500,7 +551,7 @@ export class TelemetrySubscriber {
private zone: NgZone; private zone: NgZone;
private tsOffset = 0; private tsOffset = undefined;
public subscriptionCommands: Array<WebsocketCmd>; public subscriptionCommands: Array<WebsocketCmd>;
@ -556,8 +607,14 @@ export class TelemetrySubscriber {
this.reconnectSubject.complete(); this.reconnectSubject.complete();
} }
public setTsOffset(tsOffset: number) { public setTsOffset(tsOffset: number): boolean {
this.tsOffset = tsOffset; if (this.tsOffset !== tsOffset) {
const changed = !isUndefined(this.tsOffset);
this.tsOffset = tsOffset;
return changed;
} else {
return false;
}
} }
public onData(message: SubscriptionUpdate) { public onData(message: SubscriptionUpdate) {
@ -598,6 +655,9 @@ export class TelemetrySubscriber {
} }
public onAlarmData(message: AlarmDataUpdate) { public onAlarmData(message: AlarmDataUpdate) {
if (this.tsOffset) {
message.prepareData(this.tsOffset);
}
if (this.zone) { if (this.zone) {
this.zone.run( this.zone.run(
() => { () => {

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

@ -318,6 +318,16 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
return historyTimewindow; return historyTimewindow;
} }
export function calculateTsOffset(timezone?: string): number {
if (timezone) {
const tz = getTimezone(timezone);
const localOffset = moment().utcOffset();
return (tz.utcOffset() - localOffset) * 60 * 1000;
} else {
return 0;
}
}
export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean, export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean,
timeService: TimeService): SubscriptionTimewindow { timeService: TimeService): SubscriptionTimewindow {
const subscriptionTimewindow: SubscriptionTimewindow = { const subscriptionTimewindow: SubscriptionTimewindow = {
@ -329,13 +339,8 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
type: AggregationType.AVG type: AggregationType.AVG
}, },
timezone: timewindow.timezone, timezone: timewindow.timezone,
tsOffset: 0 tsOffset: calculateTsOffset(timewindow.timezone)
}; };
if (timewindow.timezone) {
const tz = getTimezone(timewindow.timezone);
const localOffset = moment().utcOffset();
subscriptionTimewindow.tsOffset = (tz.utcOffset() - localOffset) * 60 * 1000;
}
let aggTimewindow = 0; let aggTimewindow = 0;
if (stateData) { if (stateData) {
subscriptionTimewindow.aggregation.type = AggregationType.NONE; subscriptionTimewindow.aggregation.type = AggregationType.NONE;
@ -407,6 +412,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate) endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate)
}; };
aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
subscriptionTimewindow.quickInterval = timewindow.history.quickInterval;
} else { } else {
subscriptionTimewindow.fixedWindow = { subscriptionTimewindow.fixedWindow = {
startTimeMs: timewindow.history.fixedTimewindow.startTimeMs - subscriptionTimewindow.tsOffset, startTimeMs: timewindow.history.fixedTimewindow.startTimeMs - subscriptionTimewindow.tsOffset,
@ -768,6 +774,11 @@ export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string,
return foundTimezone; return foundTimezone;
} }
export function getDefaultTimezoneInfo(): TimezoneInfo {
const userTimezone = getDefaultTimezone();
return getTimezoneInfo(userTimezone);
}
export function getDefaultTimezone(): string { export function getDefaultTimezone(): string {
if (!defaultTimezone) { if (!defaultTimezone) {
defaultTimezone = monentTz.tz.guess(); defaultTimezone = monentTz.tz.guess();

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

@ -1988,7 +1988,8 @@
"timezone": "Timezone", "timezone": "Timezone",
"select-timezone": "Select timezone", "select-timezone": "Select timezone",
"no-timezones-matching": "No timezones matching '{{timezone}}' were found.", "no-timezones-matching": "No timezones matching '{{timezone}}' were found.",
"timezone-required": "Timezone is required." "timezone-required": "Timezone is required.",
"browser-time": "Browser Time"
}, },
"queue": { "queue": {
"select_name": "Select queue name", "select_name": "Select queue name",

Loading…
Cancel
Save