Commit a17c5b77578e8bfed9545f91ea39796adfd864d2
1 parent
4b0cf383
Improve timezone offset handling for latest values and alarm widgets. Improve ti…
…mezone selector - add local browser time info on empty value.
Showing
9 changed files
with
233 additions
and
46 deletions
@@ -130,6 +130,7 @@ export class AlarmDataSubscription { | @@ -130,6 +130,7 @@ export class AlarmDataSubscription { | ||
130 | this.alarmDataCommand.query.pageLink.timeWindow = this.subsTw.realtimeWindowMs; | 130 | this.alarmDataCommand.query.pageLink.timeWindow = this.subsTw.realtimeWindowMs; |
131 | } | 131 | } |
132 | 132 | ||
133 | + this.subscriber.setTsOffset(this.subsTw.tsOffset); | ||
133 | this.subscriber.subscriptionCommands.push(this.alarmDataCommand); | 134 | this.subscriber.subscriptionCommands.push(this.alarmDataCommand); |
134 | 135 | ||
135 | this.subscriber.alarmData$.subscribe((alarmDataUpdate) => { | 136 | this.subscriber.alarmData$.subscribe((alarmDataUpdate) => { |
@@ -143,8 +144,11 @@ export class AlarmDataSubscription { | @@ -143,8 +144,11 @@ export class AlarmDataSubscription { | ||
143 | this.subscriber.subscribe(); | 144 | this.subscriber.subscribe(); |
144 | 145 | ||
145 | } else if (this.datasourceType === DatasourceType.function) { | 146 | } else if (this.datasourceType === DatasourceType.function) { |
147 | + const alarm = deepClone(simulatedAlarm); | ||
148 | + alarm.createdTime += this.subsTw.tsOffset; | ||
149 | + alarm.startTs += this.subsTw.tsOffset; | ||
146 | const pageData: PageData<AlarmData> = { | 150 | const pageData: PageData<AlarmData> = { |
147 | - data: [{...simulatedAlarm, entityId: '1', latest: {}}], | 151 | + data: [{...alarm, entityId: '1', latest: {}}], |
148 | hasNext: false, | 152 | hasNext: false, |
149 | totalElements: 1, | 153 | totalElements: 1, |
150 | totalPages: 1 | 154 | totalPages: 1 |
@@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
15 | /// | 15 | /// |
16 | 16 | ||
17 | import { DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models'; | 17 | import { DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models'; |
18 | -import { AggregationType, SubscriptionTimewindow } from '@shared/models/time/time.models'; | 18 | +import { AggregationType, getCurrentTime, SubscriptionTimewindow } from '@shared/models/time/time.models'; |
19 | import { | 19 | import { |
20 | EntityData, | 20 | EntityData, |
21 | EntityDataPageLink, | 21 | EntityDataPageLink, |
@@ -74,6 +74,7 @@ export interface EntityDataSubscriptionOptions { | @@ -74,6 +74,7 @@ export interface EntityDataSubscriptionOptions { | ||
74 | keyFilters?: Array<KeyFilter>; | 74 | keyFilters?: Array<KeyFilter>; |
75 | additionalKeyFilters?: Array<KeyFilter>; | 75 | additionalKeyFilters?: Array<KeyFilter>; |
76 | subscriptionTimewindow?: SubscriptionTimewindow; | 76 | subscriptionTimewindow?: SubscriptionTimewindow; |
77 | + latestTsOffset?: number; | ||
77 | } | 78 | } |
78 | 79 | ||
79 | export class EntityDataSubscription { | 80 | export class EntityDataSubscription { |
@@ -95,6 +96,7 @@ export class EntityDataSubscription { | @@ -95,6 +96,7 @@ export class EntityDataSubscription { | ||
95 | private entityDataResolveSubject: Subject<EntityDataLoadResult>; | 96 | private entityDataResolveSubject: Subject<EntityDataLoadResult>; |
96 | private pageData: PageData<EntityData>; | 97 | private pageData: PageData<EntityData>; |
97 | private subsTw: SubscriptionTimewindow; | 98 | private subsTw: SubscriptionTimewindow; |
99 | + private latestTsOffset: number; | ||
98 | private dataAggregators: Array<DataAggregator>; | 100 | private dataAggregators: Array<DataAggregator>; |
99 | private dataKeys: {[key: string]: Array<SubscriptionDataKey> | SubscriptionDataKey} = {}; | 101 | private dataKeys: {[key: string]: Array<SubscriptionDataKey> | SubscriptionDataKey} = {}; |
100 | private datasourceData: {[index: number]: {[key: string]: DataSetHolder}}; | 102 | private datasourceData: {[index: number]: {[key: string]: DataSetHolder}}; |
@@ -177,6 +179,7 @@ export class EntityDataSubscription { | @@ -177,6 +179,7 @@ export class EntityDataSubscription { | ||
177 | this.started = true; | 179 | this.started = true; |
178 | this.dataResolved = true; | 180 | this.dataResolved = true; |
179 | this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; | 181 | this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; |
182 | + this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; | ||
180 | this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && | 183 | this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && |
181 | isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); | 184 | isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); |
182 | this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && | 185 | this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && |
@@ -237,7 +240,12 @@ export class EntityDataSubscription { | @@ -237,7 +240,12 @@ export class EntityDataSubscription { | ||
237 | }; | 240 | }; |
238 | 241 | ||
239 | if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { | 242 | if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { |
240 | - this.prepareSubscriptionCommands(); | 243 | + this.prepareSubscriptionCommands(this.dataCommand); |
244 | + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { | ||
245 | + this.subscriber.setTsOffset(this.subsTw.tsOffset); | ||
246 | + } else { | ||
247 | + this.subscriber.setTsOffset(this.latestTsOffset); | ||
248 | + } | ||
241 | } | 249 | } |
242 | 250 | ||
243 | this.subscriber.subscriptionCommands.push(this.dataCommand); | 251 | this.subscriber.subscriptionCommands.push(this.dataCommand); |
@@ -276,9 +284,15 @@ export class EntityDataSubscription { | @@ -276,9 +284,15 @@ export class EntityDataSubscription { | ||
276 | this.subscriber.subscriptionCommands = [this.dataCommand]; | 284 | this.subscriber.subscriptionCommands = [this.dataCommand]; |
277 | } | 285 | } |
278 | }); | 286 | }); |
279 | - | ||
280 | this.subscriber.subscribe(); | 287 | this.subscriber.subscribe(); |
281 | } else if (this.datasourceType === DatasourceType.function) { | 288 | } else if (this.datasourceType === DatasourceType.function) { |
289 | + let tsOffset = 0; | ||
290 | + if (this.entityDataSubscriptionOptions.type === widgetType.latest) { | ||
291 | + tsOffset = this.entityDataSubscriptionOptions.latestTsOffset; | ||
292 | + } else if (this.entityDataSubscriptionOptions.subscriptionTimewindow) { | ||
293 | + tsOffset = this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; | ||
294 | + } | ||
295 | + | ||
282 | const entityData: EntityData = { | 296 | const entityData: EntityData = { |
283 | entityId: { | 297 | entityId: { |
284 | id: NULL_UUID, | 298 | id: NULL_UUID, |
@@ -289,7 +303,7 @@ export class EntityDataSubscription { | @@ -289,7 +303,7 @@ export class EntityDataSubscription { | ||
289 | }; | 303 | }; |
290 | const name = DatasourceType.function; | 304 | const name = DatasourceType.function; |
291 | entityData.latest[EntityKeyType.ENTITY_FIELD] = { | 305 | entityData.latest[EntityKeyType.ENTITY_FIELD] = { |
292 | - name: {ts: Date.now(), value: name} | 306 | + name: {ts: Date.now() + tsOffset, value: name} |
293 | }; | 307 | }; |
294 | const pageData: PageData<EntityData> = { | 308 | const pageData: PageData<EntityData> = { |
295 | data: [entityData], | 309 | data: [entityData], |
@@ -299,7 +313,9 @@ export class EntityDataSubscription { | @@ -299,7 +313,9 @@ export class EntityDataSubscription { | ||
299 | }; | 313 | }; |
300 | this.onPageData(pageData); | 314 | this.onPageData(pageData); |
301 | } else if (this.datasourceType === DatasourceType.entityCount) { | 315 | } else if (this.datasourceType === DatasourceType.entityCount) { |
316 | + this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; | ||
302 | this.subscriber = new TelemetrySubscriber(this.telemetryService); | 317 | this.subscriber = new TelemetrySubscriber(this.telemetryService); |
318 | + this.subscriber.setTsOffset(this.latestTsOffset); | ||
303 | this.countCommand = new EntityCountCmd(); | 319 | this.countCommand = new EntityCountCmd(); |
304 | let keyFilters = this.entityDataSubscriptionOptions.keyFilters; | 320 | let keyFilters = this.entityDataSubscriptionOptions.keyFilters; |
305 | if (this.entityDataSubscriptionOptions.additionalKeyFilters) { | 321 | if (this.entityDataSubscriptionOptions.additionalKeyFilters) { |
@@ -332,13 +348,13 @@ export class EntityDataSubscription { | @@ -332,13 +348,13 @@ export class EntityDataSubscription { | ||
332 | latest: { | 348 | latest: { |
333 | [EntityKeyType.ENTITY_FIELD]: { | 349 | [EntityKeyType.ENTITY_FIELD]: { |
334 | name: { | 350 | name: { |
335 | - ts: Date.now(), | 351 | + ts: Date.now() + this.latestTsOffset, |
336 | value: DatasourceType.entityCount | 352 | value: DatasourceType.entityCount |
337 | } | 353 | } |
338 | }, | 354 | }, |
339 | [EntityKeyType.COUNT]: { | 355 | [EntityKeyType.COUNT]: { |
340 | [countKey.name]: { | 356 | [countKey.name]: { |
341 | - ts: Date.now(), | 357 | + ts: Date.now() + this.latestTsOffset, |
342 | value: entityCountUpdate.count + '' | 358 | value: entityCountUpdate.count + '' |
343 | } | 359 | } |
344 | } | 360 | } |
@@ -359,7 +375,7 @@ export class EntityDataSubscription { | @@ -359,7 +375,7 @@ export class EntityDataSubscription { | ||
359 | latest: { | 375 | latest: { |
360 | [EntityKeyType.COUNT]: { | 376 | [EntityKeyType.COUNT]: { |
361 | [countKey.name]: { | 377 | [countKey.name]: { |
362 | - ts: Date.now(), | 378 | + ts: Date.now() + this.latestTsOffset, |
363 | value: entityCountUpdate.count + '' | 379 | value: entityCountUpdate.count + '' |
364 | } | 380 | } |
365 | } | 381 | } |
@@ -384,6 +400,7 @@ export class EntityDataSubscription { | @@ -384,6 +400,7 @@ export class EntityDataSubscription { | ||
384 | return; | 400 | return; |
385 | } | 401 | } |
386 | this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; | 402 | this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; |
403 | + this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; | ||
387 | this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && | 404 | this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && |
388 | isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); | 405 | isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); |
389 | this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && | 406 | this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && |
@@ -394,22 +411,38 @@ export class EntityDataSubscription { | @@ -394,22 +411,38 @@ export class EntityDataSubscription { | ||
394 | if (this.datasourceType === DatasourceType.entity) { | 411 | if (this.datasourceType === DatasourceType.entity) { |
395 | this.subsCommand = new EntityDataCmd(); | 412 | this.subsCommand = new EntityDataCmd(); |
396 | this.subsCommand.cmdId = this.dataCommand.cmdId; | 413 | this.subsCommand.cmdId = this.dataCommand.cmdId; |
397 | - this.prepareSubscriptionCommands(); | ||
398 | - if (!this.subsCommand.isEmpty()) { | 414 | + this.prepareSubscriptionCommands(this.subsCommand); |
415 | + let latestTsOffsetChanged = false; | ||
416 | + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { | ||
417 | + this.subscriber.setTsOffset(this.subsTw.tsOffset); | ||
418 | + } else { | ||
419 | + latestTsOffsetChanged = this.subscriber.setTsOffset(this.latestTsOffset); | ||
420 | + } | ||
421 | + if (latestTsOffsetChanged) { | ||
422 | + if (this.listener.initialPageDataChanged) { | ||
423 | + this.listener.initialPageDataChanged(this.pageData); | ||
424 | + } | ||
425 | + } else if (!this.subsCommand.isEmpty()) { | ||
399 | this.subscriber.subscriptionCommands = [this.subsCommand]; | 426 | this.subscriber.subscriptionCommands = [this.subsCommand]; |
400 | this.subscriber.update(); | 427 | this.subscriber.update(); |
401 | } | 428 | } |
429 | + } else if (this.datasourceType === DatasourceType.entityCount) { | ||
430 | + if (this.subscriber.setTsOffset(this.latestTsOffset)) { | ||
431 | + if (this.listener.initialPageDataChanged) { | ||
432 | + this.listener.initialPageDataChanged(this.pageData); | ||
433 | + } | ||
434 | + } | ||
402 | } else if (this.datasourceType === DatasourceType.function) { | 435 | } else if (this.datasourceType === DatasourceType.function) { |
403 | this.startFunction(); | 436 | this.startFunction(); |
404 | } | 437 | } |
405 | this.started = true; | 438 | this.started = true; |
406 | } | 439 | } |
407 | 440 | ||
408 | - private prepareSubscriptionCommands() { | 441 | + private prepareSubscriptionCommands(cmd: EntityDataCmd) { |
409 | if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { | 442 | if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { |
410 | if (this.tsFields.length > 0) { | 443 | if (this.tsFields.length > 0) { |
411 | if (this.history) { | 444 | if (this.history) { |
412 | - this.subsCommand.historyCmd = { | 445 | + cmd.historyCmd = { |
413 | keys: this.tsFields.map(key => key.key), | 446 | keys: this.tsFields.map(key => key.key), |
414 | startTs: this.subsTw.fixedWindow.startTimeMs, | 447 | startTs: this.subsTw.fixedWindow.startTimeMs, |
415 | endTs: this.subsTw.fixedWindow.endTimeMs, | 448 | endTs: this.subsTw.fixedWindow.endTimeMs, |
@@ -419,7 +452,7 @@ export class EntityDataSubscription { | @@ -419,7 +452,7 @@ export class EntityDataSubscription { | ||
419 | fetchLatestPreviousPoint: this.subsTw.aggregation.stateData | 452 | fetchLatestPreviousPoint: this.subsTw.aggregation.stateData |
420 | }; | 453 | }; |
421 | } else { | 454 | } else { |
422 | - this.subsCommand.tsCmd = { | 455 | + cmd.tsCmd = { |
423 | keys: this.tsFields.map(key => key.key), | 456 | keys: this.tsFields.map(key => key.key), |
424 | startTs: this.subsTw.startTs, | 457 | startTs: this.subsTw.startTs, |
425 | timeWindow: this.subsTw.aggregation.timeWindow, | 458 | timeWindow: this.subsTw.aggregation.timeWindow, |
@@ -430,10 +463,9 @@ export class EntityDataSubscription { | @@ -430,10 +463,9 @@ export class EntityDataSubscription { | ||
430 | }; | 463 | }; |
431 | } | 464 | } |
432 | } | 465 | } |
433 | - this.subscriber.setTsOffset(this.subsTw.tsOffset); | ||
434 | } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { | 466 | } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { |
435 | if (this.latestValues.length > 0) { | 467 | if (this.latestValues.length > 0) { |
436 | - this.subsCommand.latestCmd = { | 468 | + cmd.latestCmd = { |
437 | keys: this.latestValues | 469 | keys: this.latestValues |
438 | }; | 470 | }; |
439 | } | 471 | } |
@@ -783,7 +815,7 @@ export class EntityDataSubscription { | @@ -783,7 +815,7 @@ export class EntityDataSubscription { | ||
783 | } else { | 815 | } else { |
784 | prevSeries = [0, 0]; | 816 | prevSeries = [0, 0]; |
785 | } | 817 | } |
786 | - const time = Date.now(); | 818 | + const time = Date.now() + this.latestTsOffset; |
787 | const value = dataKey.func(time, prevSeries[1]); | 819 | const value = dataKey.func(time, prevSeries[1]); |
788 | const series: [number, any] = [time, value]; | 820 | const series: [number, any] = [time, value]; |
789 | this.datasourceData[0][dataKey.key].data = [series]; | 821 | this.datasourceData[0][dataKey.key].data = [series]; |
@@ -838,6 +870,10 @@ export class EntityDataSubscription { | @@ -838,6 +870,10 @@ export class EntityDataSubscription { | ||
838 | endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs + | 870 | endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs + |
839 | this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; | 871 | this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; |
840 | } | 872 | } |
873 | + if (this.entityDataSubscriptionOptions.subscriptionTimewindow.quickInterval) { | ||
874 | + const currentTime = getCurrentTime().valueOf() + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; | ||
875 | + endTime = Math.min(currentTime, endTime); | ||
876 | + } | ||
841 | } | 877 | } |
842 | generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime); | 878 | generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime); |
843 | } | 879 | } |
@@ -32,6 +32,7 @@ import { Observable, of } from 'rxjs'; | @@ -32,6 +32,7 @@ import { Observable, of } from 'rxjs'; | ||
32 | export interface EntityDataListener { | 32 | export interface EntityDataListener { |
33 | subscriptionType: widgetType; | 33 | subscriptionType: widgetType; |
34 | subscriptionTimewindow?: SubscriptionTimewindow; | 34 | subscriptionTimewindow?: SubscriptionTimewindow; |
35 | + latestTsOffset?: number; | ||
35 | configDatasource: Datasource; | 36 | configDatasource: Datasource; |
36 | configDatasourceIndex: number; | 37 | configDatasourceIndex: number; |
37 | dataLoaded: (pageData: PageData<EntityData>, | 38 | dataLoaded: (pageData: PageData<EntityData>, |
@@ -92,6 +93,8 @@ export class EntityDataService { | @@ -92,6 +93,8 @@ export class EntityDataService { | ||
92 | if (listener.subscription) { | 93 | if (listener.subscription) { |
93 | if (listener.subscriptionType === widgetType.timeseries) { | 94 | if (listener.subscriptionType === widgetType.timeseries) { |
94 | listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); | 95 | listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); |
96 | + } else if (listener.subscriptionType === widgetType.latest) { | ||
97 | + listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; | ||
95 | } | 98 | } |
96 | listener.subscription.start(); | 99 | listener.subscription.start(); |
97 | } | 100 | } |
@@ -118,6 +121,8 @@ export class EntityDataService { | @@ -118,6 +121,8 @@ export class EntityDataService { | ||
118 | listener.subscription = new EntityDataSubscription(listener, this.telemetryService, this.utils); | 121 | listener.subscription = new EntityDataSubscription(listener, this.telemetryService, this.utils); |
119 | if (listener.subscriptionType === widgetType.timeseries) { | 122 | if (listener.subscriptionType === widgetType.timeseries) { |
120 | listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); | 123 | listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); |
124 | + } else if (listener.subscriptionType === widgetType.latest) { | ||
125 | + listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; | ||
121 | } | 126 | } |
122 | return listener.subscription.subscribe(); | 127 | return listener.subscription.subscribe(); |
123 | } | 128 | } |
@@ -39,8 +39,10 @@ import { HttpErrorResponse } from '@angular/common/http'; | @@ -39,8 +39,10 @@ import { HttpErrorResponse } from '@angular/common/http'; | ||
39 | import { | 39 | import { |
40 | calculateIntervalEndTime, | 40 | calculateIntervalEndTime, |
41 | calculateIntervalStartTime, | 41 | calculateIntervalStartTime, |
42 | + calculateTsOffset, | ||
42 | createSubscriptionTimewindow, | 43 | createSubscriptionTimewindow, |
43 | - createTimewindowForComparison, getCurrentTime, | 44 | + createTimewindowForComparison, |
45 | + getCurrentTime, | ||
44 | SubscriptionTimewindow, | 46 | SubscriptionTimewindow, |
45 | Timewindow, | 47 | Timewindow, |
46 | toHistoryTimewindow, | 48 | toHistoryTimewindow, |
@@ -79,8 +81,10 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -79,8 +81,10 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
79 | timeWindow: WidgetTimewindow; | 81 | timeWindow: WidgetTimewindow; |
80 | originalTimewindow: Timewindow; | 82 | originalTimewindow: Timewindow; |
81 | timeWindowConfig: Timewindow; | 83 | timeWindowConfig: Timewindow; |
84 | + timezone: string; | ||
82 | subscriptionTimewindow: SubscriptionTimewindow; | 85 | subscriptionTimewindow: SubscriptionTimewindow; |
83 | useDashboardTimewindow: boolean; | 86 | useDashboardTimewindow: boolean; |
87 | + tsOffset = 0; | ||
84 | 88 | ||
85 | hasDataPageLink: boolean; | 89 | hasDataPageLink: boolean; |
86 | singleEntity: boolean; | 90 | singleEntity: boolean; |
@@ -213,6 +217,10 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -213,6 +217,10 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
213 | this.timeWindow = {}; | 217 | this.timeWindow = {}; |
214 | this.useDashboardTimewindow = options.useDashboardTimewindow; | 218 | this.useDashboardTimewindow = options.useDashboardTimewindow; |
215 | this.stateData = options.stateData; | 219 | this.stateData = options.stateData; |
220 | + if (this.type === widgetType.latest) { | ||
221 | + this.timezone = options.dashboardTimewindow.timezone; | ||
222 | + this.updateTsOffset(); | ||
223 | + } | ||
216 | if (this.useDashboardTimewindow) { | 224 | if (this.useDashboardTimewindow) { |
217 | this.timeWindowConfig = deepClone(options.dashboardTimewindow); | 225 | this.timeWindowConfig = deepClone(options.dashboardTimewindow); |
218 | } else { | 226 | } else { |
@@ -578,11 +586,16 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -578,11 +586,16 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
578 | if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { | 586 | if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { |
579 | this.timeWindowConfig = deepClone(newDashboardTimewindow); | 587 | this.timeWindowConfig = deepClone(newDashboardTimewindow); |
580 | this.update(); | 588 | this.update(); |
581 | - return true; | 589 | + } |
590 | + } | ||
591 | + } else if (this.type === widgetType.latest) { | ||
592 | + if (newDashboardTimewindow && this.timezone !== newDashboardTimewindow.timezone) { | ||
593 | + this.timezone = newDashboardTimewindow.timezone; | ||
594 | + if (this.updateTsOffset()) { | ||
595 | + this.update(); | ||
582 | } | 596 | } |
583 | } | 597 | } |
584 | } | 598 | } |
585 | - return false; | ||
586 | } | 599 | } |
587 | 600 | ||
588 | updateDataVisibility(index: number): void { | 601 | updateDataVisibility(index: number): void { |
@@ -815,6 +828,7 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -815,6 +828,7 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
815 | configDatasource: datasource, | 828 | configDatasource: datasource, |
816 | configDatasourceIndex: datasourceIndex, | 829 | configDatasourceIndex: datasourceIndex, |
817 | subscriptionTimewindow: this.subscriptionTimewindow, | 830 | subscriptionTimewindow: this.subscriptionTimewindow, |
831 | + latestTsOffset: this.tsOffset, | ||
818 | dataLoaded: (pageData, data1, datasourceIndex1, pageLink1) => { | 832 | dataLoaded: (pageData, data1, datasourceIndex1, pageLink1) => { |
819 | this.dataLoaded(pageData, data1, datasourceIndex1, pageLink1, true); | 833 | this.dataLoaded(pageData, data1, datasourceIndex1, pageLink1, true); |
820 | }, | 834 | }, |
@@ -882,25 +896,28 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -882,25 +896,28 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
882 | } | 896 | } |
883 | 897 | ||
884 | private dataSubscribe() { | 898 | private dataSubscribe() { |
899 | + this.updateDataTimewindow(); | ||
885 | if (!this.hasDataPageLink) { | 900 | if (!this.hasDataPageLink) { |
886 | - if (this.type === widgetType.timeseries && this.timeWindowConfig) { | ||
887 | - this.updateDataTimewindow(); | ||
888 | - if (this.subscriptionTimewindow.fixedWindow) { | 901 | + if (this.type === widgetType.timeseries && this.timeWindowConfig && this.subscriptionTimewindow.fixedWindow) { |
889 | this.onDataUpdated(); | 902 | this.onDataUpdated(); |
890 | - } | ||
891 | } | 903 | } |
892 | const forceUpdate = !this.datasources.length; | 904 | const forceUpdate = !this.datasources.length; |
905 | + const notifyDataLoaded = !this.entityDataListeners.filter((listener) => listener.subscription ? true : false).length; | ||
893 | this.entityDataListeners.forEach((listener) => { | 906 | this.entityDataListeners.forEach((listener) => { |
894 | if (this.comparisonEnabled && listener.configDatasource.isAdditional) { | 907 | if (this.comparisonEnabled && listener.configDatasource.isAdditional) { |
895 | listener.subscriptionTimewindow = this.timewindowForComparison; | 908 | listener.subscriptionTimewindow = this.timewindowForComparison; |
896 | } else { | 909 | } else { |
897 | listener.subscriptionTimewindow = this.subscriptionTimewindow; | 910 | listener.subscriptionTimewindow = this.subscriptionTimewindow; |
911 | + listener.latestTsOffset = this.tsOffset; | ||
898 | } | 912 | } |
899 | this.ctx.entityDataService.startSubscription(listener); | 913 | this.ctx.entityDataService.startSubscription(listener); |
900 | }); | 914 | }); |
901 | if (forceUpdate) { | 915 | if (forceUpdate) { |
902 | this.onDataUpdated(); | 916 | this.onDataUpdated(); |
903 | } | 917 | } |
918 | + if (notifyDataLoaded) { | ||
919 | + this.notifyDataLoaded(); | ||
920 | + } | ||
904 | } | 921 | } |
905 | } | 922 | } |
906 | 923 | ||
@@ -1102,6 +1119,15 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -1102,6 +1119,15 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
1102 | } | 1119 | } |
1103 | } | 1120 | } |
1104 | 1121 | ||
1122 | + private updateTsOffset(): boolean { | ||
1123 | + const newOffset = calculateTsOffset(this.timezone); | ||
1124 | + if (this.tsOffset !== newOffset) { | ||
1125 | + this.tsOffset = newOffset; | ||
1126 | + return true; | ||
1127 | + } | ||
1128 | + return false; | ||
1129 | + } | ||
1130 | + | ||
1105 | private updateRealtimeSubscription(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow { | 1131 | private updateRealtimeSubscription(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow { |
1106 | if (subscriptionTimewindow) { | 1132 | if (subscriptionTimewindow) { |
1107 | this.subscriptionTimewindow = subscriptionTimewindow; | 1133 | this.subscriptionTimewindow = subscriptionTimewindow; |
@@ -133,7 +133,7 @@ | @@ -133,7 +133,7 @@ | ||
133 | <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggInterval" | 133 | <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggInterval" |
134 | (ngModelChange)="onHideAggIntervalChanged()"></mat-checkbox> | 134 | (ngModelChange)="onHideAggIntervalChanged()"></mat-checkbox> |
135 | </section> | 135 | </section> |
136 | - <section fxLayout="column" [fxShow]="isEdit || !timewindow.hideAggInterval"> | 136 | + <section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideAggInterval"> |
137 | <div class="limit-slider-container" | 137 | <div class="limit-slider-container" |
138 | fxLayout="row" fxLayoutAlign="start center"> | 138 | fxLayout="row" fxLayoutAlign="start center"> |
139 | <span translate>aggregation.limit</span> | 139 | <span translate>aggregation.limit</span> |
@@ -184,7 +184,8 @@ | @@ -184,7 +184,8 @@ | ||
184 | (ngModelChange)="onHideTimezoneChanged()"></mat-checkbox> | 184 | (ngModelChange)="onHideTimezoneChanged()"></mat-checkbox> |
185 | </section> | 185 | </section> |
186 | <tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone" | 186 | <tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone" |
187 | - formControlName="timezone"> | 187 | + localBrowserTimezonePlaceholderOnEmpty="true" |
188 | + formControlName="timezone"> | ||
188 | </tb-timezone-select> | 189 | </tb-timezone-select> |
189 | </div> | 190 | </div> |
190 | <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> | 191 | <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> |
@@ -23,7 +23,8 @@ import { AppState } from '@app/core/core.state'; | @@ -23,7 +23,8 @@ import { AppState } from '@app/core/core.state'; | ||
23 | import { TranslateService } from '@ngx-translate/core'; | 23 | import { TranslateService } from '@ngx-translate/core'; |
24 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; | 24 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
25 | import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; | 25 | import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; |
26 | -import { getTimezoneInfo, getTimezones, TimezoneInfo } from '@shared/models/time/time.models'; | 26 | +import { getDefaultTimezoneInfo, getTimezoneInfo, getTimezones, TimezoneInfo } from '@shared/models/time/time.models'; |
27 | +import { deepClone } from '@core/utils'; | ||
27 | 28 | ||
28 | @Component({ | 29 | @Component({ |
29 | selector: 'tb-timezone-select', | 30 | selector: 'tb-timezone-select', |
@@ -68,6 +69,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -68,6 +69,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
68 | this.userTimezoneByDefaultValue = coerceBooleanProperty(value); | 69 | this.userTimezoneByDefaultValue = coerceBooleanProperty(value); |
69 | } | 70 | } |
70 | 71 | ||
72 | + private localBrowserTimezonePlaceholderOnEmptyValue: boolean; | ||
73 | + get localBrowserTimezonePlaceholderOnEmpty(): boolean { | ||
74 | + return this.localBrowserTimezonePlaceholderOnEmptyValue; | ||
75 | + } | ||
76 | + @Input() | ||
77 | + set localBrowserTimezonePlaceholderOnEmpty(value: boolean) { | ||
78 | + this.localBrowserTimezonePlaceholderOnEmptyValue = coerceBooleanProperty(value); | ||
79 | + } | ||
80 | + | ||
71 | @Input() | 81 | @Input() |
72 | disabled: boolean; | 82 | disabled: boolean; |
73 | 83 | ||
@@ -81,6 +91,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -81,6 +91,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
81 | 91 | ||
82 | private dirty = false; | 92 | private dirty = false; |
83 | 93 | ||
94 | + private localBrowserTimezoneInfoPlaceholder: TimezoneInfo; | ||
95 | + | ||
96 | + private timezones: Array<TimezoneInfo>; | ||
97 | + | ||
84 | private propagateChange = (v: any) => { }; | 98 | private propagateChange = (v: any) => { }; |
85 | 99 | ||
86 | constructor(private store: Store<AppState>, | 100 | constructor(private store: Store<AppState>, |
@@ -146,7 +160,11 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -146,7 +160,11 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
146 | } | 160 | } |
147 | } else { | 161 | } else { |
148 | this.modelValue = null; | 162 | this.modelValue = null; |
149 | - this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false}); | 163 | + if (this.localBrowserTimezonePlaceholderOnEmptyValue) { |
164 | + this.selectTimezoneFormGroup.get('timezone').patchValue(this.getLocalBrowserTimezoneInfoPlaceholder(), {emitEvent: false}); | ||
165 | + } else { | ||
166 | + this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false}); | ||
167 | + } | ||
150 | } | 168 | } |
151 | this.dirty = true; | 169 | this.dirty = true; |
152 | } | 170 | } |
@@ -162,11 +180,17 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -162,11 +180,17 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
162 | if (this.ignoreClosePanel) { | 180 | if (this.ignoreClosePanel) { |
163 | this.ignoreClosePanel = false; | 181 | this.ignoreClosePanel = false; |
164 | } else { | 182 | } else { |
165 | - if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) { | ||
166 | - const defaultTimezoneInfo = getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue); | ||
167 | - if (defaultTimezoneInfo !== null) { | 183 | + if (!this.modelValue) { |
184 | + if (this.defaultTimezoneId || this.userTimezoneByDefaultValue) { | ||
185 | + const defaultTimezoneInfo = getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue); | ||
186 | + if (defaultTimezoneInfo !== null) { | ||
187 | + this.ngZone.run(() => { | ||
188 | + this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true}); | ||
189 | + }); | ||
190 | + } | ||
191 | + } else if (this.localBrowserTimezonePlaceholderOnEmptyValue) { | ||
168 | this.ngZone.run(() => { | 192 | this.ngZone.run(() => { |
169 | - this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true}); | 193 | + this.selectTimezoneFormGroup.get('timezone').reset(this.getLocalBrowserTimezoneInfoPlaceholder(), {emitEvent: true}); |
170 | }); | 194 | }); |
171 | } | 195 | } |
172 | } | 196 | } |
@@ -187,10 +211,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -187,10 +211,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
187 | fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { | 211 | fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { |
188 | this.searchText = searchText; | 212 | this.searchText = searchText; |
189 | if (searchText && searchText.length) { | 213 | if (searchText && searchText.length) { |
190 | - return of(getTimezones().filter((timezoneInfo) => | 214 | + return of(this.loadTimezones().filter((timezoneInfo) => |
191 | timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase()))); | 215 | timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase()))); |
192 | } | 216 | } |
193 | - return of(getTimezones()); | 217 | + return of(this.loadTimezones()); |
194 | } | 218 | } |
195 | 219 | ||
196 | clear() { | 220 | clear() { |
@@ -200,4 +224,23 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -200,4 +224,23 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
200 | }, 0); | 224 | }, 0); |
201 | } | 225 | } |
202 | 226 | ||
227 | + private loadTimezones(): Array<TimezoneInfo> { | ||
228 | + if (!this.timezones) { | ||
229 | + this.timezones = []; | ||
230 | + if (this.localBrowserTimezonePlaceholderOnEmptyValue) { | ||
231 | + this.timezones.push(this.getLocalBrowserTimezoneInfoPlaceholder()); | ||
232 | + } | ||
233 | + this.timezones.push(...getTimezones()); | ||
234 | + } | ||
235 | + return this.timezones; | ||
236 | + } | ||
237 | + | ||
238 | + private getLocalBrowserTimezoneInfoPlaceholder(): TimezoneInfo { | ||
239 | + if (!this.localBrowserTimezoneInfoPlaceholder) { | ||
240 | + this.localBrowserTimezoneInfoPlaceholder = deepClone(getDefaultTimezoneInfo()); | ||
241 | + this.localBrowserTimezoneInfoPlaceholder.id = null; | ||
242 | + this.localBrowserTimezoneInfoPlaceholder.name = this.translate.instant('timezone.browser-time'); | ||
243 | + } | ||
244 | + return this.localBrowserTimezoneInfoPlaceholder; | ||
245 | + } | ||
203 | } | 246 | } |
@@ -30,6 +30,9 @@ import { | @@ -30,6 +30,9 @@ import { | ||
30 | TsValue | 30 | TsValue |
31 | } from '@shared/models/query/query.models'; | 31 | } from '@shared/models/query/query.models'; |
32 | import { PageData } from '@shared/models/page/page-data'; | 32 | import { PageData } from '@shared/models/page/page-data'; |
33 | +import { alarmFields } from '@shared/models/alarm.models'; | ||
34 | +import { entityFields } from '@shared/models/entity.models'; | ||
35 | +import { isUndefined } from '@core/utils'; | ||
33 | 36 | ||
34 | export enum DataKeyType { | 37 | export enum DataKeyType { |
35 | timeseries = 'timeseries', | 38 | timeseries = 'timeseries', |
@@ -446,7 +449,9 @@ export class EntityDataUpdate extends DataUpdate<EntityData> { | @@ -446,7 +449,9 @@ export class EntityDataUpdate extends DataUpdate<EntityData> { | ||
446 | for (const key of Object.keys(entityData.timeseries)) { | 449 | for (const key of Object.keys(entityData.timeseries)) { |
447 | const tsValues = entityData.timeseries[key]; | 450 | const tsValues = entityData.timeseries[key]; |
448 | for (const tsValue of tsValues) { | 451 | for (const tsValue of tsValues) { |
449 | - tsValue.ts += tsOffset; | 452 | + if (tsValue.ts) { |
453 | + tsValue.ts += tsOffset; | ||
454 | + } | ||
450 | } | 455 | } |
451 | } | 456 | } |
452 | } | 457 | } |
@@ -455,13 +460,17 @@ export class EntityDataUpdate extends DataUpdate<EntityData> { | @@ -455,13 +460,17 @@ export class EntityDataUpdate extends DataUpdate<EntityData> { | ||
455 | const keyTypeValues = entityData.latest[entityKeyType]; | 460 | const keyTypeValues = entityData.latest[entityKeyType]; |
456 | for (const key of Object.keys(keyTypeValues)) { | 461 | for (const key of Object.keys(keyTypeValues)) { |
457 | const tsValue = keyTypeValues[key]; | 462 | const tsValue = keyTypeValues[key]; |
458 | - tsValue.ts += tsOffset; | 463 | + if (tsValue.ts) { |
464 | + tsValue.ts += tsOffset; | ||
465 | + } | ||
466 | + if (key === entityFields.createdTime.keyName && tsValue.value) { | ||
467 | + tsValue.value = (Number(tsValue.value) + tsOffset) + ''; | ||
468 | + } | ||
459 | } | 469 | } |
460 | } | 470 | } |
461 | } | 471 | } |
462 | } | 472 | } |
463 | } | 473 | } |
464 | - | ||
465 | } | 474 | } |
466 | 475 | ||
467 | export class AlarmDataUpdate extends DataUpdate<AlarmData> { | 476 | export class AlarmDataUpdate extends DataUpdate<AlarmData> { |
@@ -473,6 +482,48 @@ export class AlarmDataUpdate extends DataUpdate<AlarmData> { | @@ -473,6 +482,48 @@ export class AlarmDataUpdate extends DataUpdate<AlarmData> { | ||
473 | this.allowedEntities = msg.allowedEntities; | 482 | this.allowedEntities = msg.allowedEntities; |
474 | this.totalEntities = msg.totalEntities; | 483 | this.totalEntities = msg.totalEntities; |
475 | } | 484 | } |
485 | + | ||
486 | + public prepareData(tsOffset: number) { | ||
487 | + if (this.data) { | ||
488 | + this.processAlarmData(this.data.data, tsOffset); | ||
489 | + } | ||
490 | + if (this.update) { | ||
491 | + this.processAlarmData(this.update, tsOffset); | ||
492 | + } | ||
493 | + } | ||
494 | + | ||
495 | + private processAlarmData(data: Array<AlarmData>, tsOffset: number) { | ||
496 | + for (const alarmData of data) { | ||
497 | + alarmData.createdTime += tsOffset; | ||
498 | + if (alarmData.ackTs) { | ||
499 | + alarmData.ackTs += tsOffset; | ||
500 | + } | ||
501 | + if (alarmData.clearTs) { | ||
502 | + alarmData.clearTs += tsOffset; | ||
503 | + } | ||
504 | + if (alarmData.endTs) { | ||
505 | + alarmData.endTs += tsOffset; | ||
506 | + } | ||
507 | + if (alarmData.latest) { | ||
508 | + for (const entityKeyType of Object.keys(alarmData.latest)) { | ||
509 | + const keyTypeValues = alarmData.latest[entityKeyType]; | ||
510 | + for (const key of Object.keys(keyTypeValues)) { | ||
511 | + const tsValue = keyTypeValues[key]; | ||
512 | + if (tsValue.ts) { | ||
513 | + tsValue.ts += tsOffset; | ||
514 | + } | ||
515 | + if (key in [entityFields.createdTime.keyName, | ||
516 | + alarmFields.startTime.keyName, | ||
517 | + alarmFields.endTime.keyName, | ||
518 | + alarmFields.ackTime.keyName, | ||
519 | + alarmFields.clearTime.keyName] && tsValue.value) { | ||
520 | + tsValue.value = (Number(tsValue.value) + tsOffset) + ''; | ||
521 | + } | ||
522 | + } | ||
523 | + } | ||
524 | + } | ||
525 | + } | ||
526 | + } | ||
476 | } | 527 | } |
477 | 528 | ||
478 | export class EntityCountUpdate extends CmdUpdate { | 529 | export class EntityCountUpdate extends CmdUpdate { |
@@ -500,7 +551,7 @@ export class TelemetrySubscriber { | @@ -500,7 +551,7 @@ export class TelemetrySubscriber { | ||
500 | 551 | ||
501 | private zone: NgZone; | 552 | private zone: NgZone; |
502 | 553 | ||
503 | - private tsOffset = 0; | 554 | + private tsOffset = undefined; |
504 | 555 | ||
505 | public subscriptionCommands: Array<WebsocketCmd>; | 556 | public subscriptionCommands: Array<WebsocketCmd>; |
506 | 557 | ||
@@ -556,8 +607,14 @@ export class TelemetrySubscriber { | @@ -556,8 +607,14 @@ export class TelemetrySubscriber { | ||
556 | this.reconnectSubject.complete(); | 607 | this.reconnectSubject.complete(); |
557 | } | 608 | } |
558 | 609 | ||
559 | - public setTsOffset(tsOffset: number) { | ||
560 | - this.tsOffset = tsOffset; | 610 | + public setTsOffset(tsOffset: number): boolean { |
611 | + if (this.tsOffset !== tsOffset) { | ||
612 | + const changed = !isUndefined(this.tsOffset); | ||
613 | + this.tsOffset = tsOffset; | ||
614 | + return changed; | ||
615 | + } else { | ||
616 | + return false; | ||
617 | + } | ||
561 | } | 618 | } |
562 | 619 | ||
563 | public onData(message: SubscriptionUpdate) { | 620 | public onData(message: SubscriptionUpdate) { |
@@ -598,6 +655,9 @@ export class TelemetrySubscriber { | @@ -598,6 +655,9 @@ export class TelemetrySubscriber { | ||
598 | } | 655 | } |
599 | 656 | ||
600 | public onAlarmData(message: AlarmDataUpdate) { | 657 | public onAlarmData(message: AlarmDataUpdate) { |
658 | + if (this.tsOffset) { | ||
659 | + message.prepareData(this.tsOffset); | ||
660 | + } | ||
601 | if (this.zone) { | 661 | if (this.zone) { |
602 | this.zone.run( | 662 | this.zone.run( |
603 | () => { | 663 | () => { |
@@ -318,6 +318,16 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | @@ -318,6 +318,16 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | ||
318 | return historyTimewindow; | 318 | return historyTimewindow; |
319 | } | 319 | } |
320 | 320 | ||
321 | +export function calculateTsOffset(timezone?: string): number { | ||
322 | + if (timezone) { | ||
323 | + const tz = getTimezone(timezone); | ||
324 | + const localOffset = moment().utcOffset(); | ||
325 | + return (tz.utcOffset() - localOffset) * 60 * 1000; | ||
326 | + } else { | ||
327 | + return 0; | ||
328 | + } | ||
329 | +} | ||
330 | + | ||
321 | export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean, | 331 | export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean, |
322 | timeService: TimeService): SubscriptionTimewindow { | 332 | timeService: TimeService): SubscriptionTimewindow { |
323 | const subscriptionTimewindow: SubscriptionTimewindow = { | 333 | const subscriptionTimewindow: SubscriptionTimewindow = { |
@@ -329,13 +339,8 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | @@ -329,13 +339,8 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | ||
329 | type: AggregationType.AVG | 339 | type: AggregationType.AVG |
330 | }, | 340 | }, |
331 | timezone: timewindow.timezone, | 341 | timezone: timewindow.timezone, |
332 | - tsOffset: 0 | 342 | + tsOffset: calculateTsOffset(timewindow.timezone) |
333 | }; | 343 | }; |
334 | - if (timewindow.timezone) { | ||
335 | - const tz = getTimezone(timewindow.timezone); | ||
336 | - const localOffset = moment().utcOffset(); | ||
337 | - subscriptionTimewindow.tsOffset = (tz.utcOffset() - localOffset) * 60 * 1000; | ||
338 | - } | ||
339 | let aggTimewindow = 0; | 344 | let aggTimewindow = 0; |
340 | if (stateData) { | 345 | if (stateData) { |
341 | subscriptionTimewindow.aggregation.type = AggregationType.NONE; | 346 | subscriptionTimewindow.aggregation.type = AggregationType.NONE; |
@@ -407,6 +412,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | @@ -407,6 +412,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | ||
407 | endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate) | 412 | endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate) |
408 | }; | 413 | }; |
409 | aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; | 414 | aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; |
415 | + subscriptionTimewindow.quickInterval = timewindow.history.quickInterval; | ||
410 | } else { | 416 | } else { |
411 | subscriptionTimewindow.fixedWindow = { | 417 | subscriptionTimewindow.fixedWindow = { |
412 | startTimeMs: timewindow.history.fixedTimewindow.startTimeMs - subscriptionTimewindow.tsOffset, | 418 | startTimeMs: timewindow.history.fixedTimewindow.startTimeMs - subscriptionTimewindow.tsOffset, |
@@ -768,6 +774,11 @@ export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, | @@ -768,6 +774,11 @@ export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, | ||
768 | return foundTimezone; | 774 | return foundTimezone; |
769 | } | 775 | } |
770 | 776 | ||
777 | +export function getDefaultTimezoneInfo(): TimezoneInfo { | ||
778 | + const userTimezone = getDefaultTimezone(); | ||
779 | + return getTimezoneInfo(userTimezone); | ||
780 | +} | ||
781 | + | ||
771 | export function getDefaultTimezone(): string { | 782 | export function getDefaultTimezone(): string { |
772 | if (!defaultTimezone) { | 783 | if (!defaultTimezone) { |
773 | defaultTimezone = monentTz.tz.guess(); | 784 | defaultTimezone = monentTz.tz.guess(); |
@@ -1988,7 +1988,8 @@ | @@ -1988,7 +1988,8 @@ | ||
1988 | "timezone": "Timezone", | 1988 | "timezone": "Timezone", |
1989 | "select-timezone": "Select timezone", | 1989 | "select-timezone": "Select timezone", |
1990 | "no-timezones-matching": "No timezones matching '{{timezone}}' were found.", | 1990 | "no-timezones-matching": "No timezones matching '{{timezone}}' were found.", |
1991 | - "timezone-required": "Timezone is required." | 1991 | + "timezone-required": "Timezone is required.", |
1992 | + "browser-time": "Browser Time" | ||
1992 | }, | 1993 | }, |
1993 | "queue": { | 1994 | "queue": { |
1994 | "select_name": "Select queue name", | 1995 | "select_name": "Select queue name", |