Commit 43578c7e584da5f487eaabde42a777ab25a1c044
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
25 changed files
with
1007 additions
and
217 deletions
@@ -283,10 +283,12 @@ public class DefaultTbClusterService implements TbClusterService { | @@ -283,10 +283,12 @@ public class DefaultTbClusterService implements TbClusterService { | ||
283 | byte[] msgBytes = encodingService.encode(msg); | 283 | byte[] msgBytes = encodingService.encode(msg); |
284 | TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); | 284 | TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); |
285 | Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); | 285 | Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); |
286 | - if (msg.getEntityId().getEntityType().equals(EntityType.TENANT) | ||
287 | - || msg.getEntityId().getEntityType().equals(EntityType.TENANT_PROFILE) | ||
288 | - || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE) | ||
289 | - || msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { | 286 | + EntityType entityType = msg.getEntityId().getEntityType(); |
287 | + if (entityType.equals(EntityType.TENANT) | ||
288 | + || entityType.equals(EntityType.TENANT_PROFILE) | ||
289 | + || entityType.equals(EntityType.DEVICE_PROFILE) | ||
290 | + || entityType.equals(EntityType.API_USAGE_STATE) | ||
291 | + || (entityType.equals(EntityType.DEVICE) && msg.getEvent() == ComponentLifecycleEvent.UPDATED)) { | ||
290 | TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); | 292 | TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); |
291 | Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); | 293 | Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); |
292 | for (String serviceId : tbCoreServices) { | 294 | for (String serviceId : tbCoreServices) { |
@@ -76,12 +76,17 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, UUID> { | @@ -76,12 +76,17 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, UUID> { | ||
76 | @Param("searchText") String searchText, | 76 | @Param("searchText") String searchText, |
77 | Pageable pageable); | 77 | Pageable pageable); |
78 | 78 | ||
79 | - @Query("SELECT alarm.severity FROM AlarmEntity alarm" + | ||
80 | - " WHERE alarm.tenantId = :tenantId" + | ||
81 | - " AND alarm.originatorId = :entityId" + | ||
82 | - " AND ((:status) IS NULL OR alarm.status in (:status))") | 79 | + @Query(value = "SELECT a.severity FROM AlarmEntity a " + |
80 | + "LEFT JOIN RelationEntity re ON a.id = re.toId " + | ||
81 | + "AND re.relationTypeGroup = 'ALARM' " + | ||
82 | + "AND re.toType = 'ALARM' " + | ||
83 | + "AND re.fromId = :affectedEntityId " + | ||
84 | + "AND re.fromType = :affectedEntityType " + | ||
85 | + "WHERE a.tenantId = :tenantId " + | ||
86 | + "AND (a.originatorId = :affectedEntityId or re.fromId IS NOT NULL) " + | ||
87 | + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses))") | ||
83 | Set<AlarmSeverity> findAlarmSeverities(@Param("tenantId") UUID tenantId, | 88 | Set<AlarmSeverity> findAlarmSeverities(@Param("tenantId") UUID tenantId, |
84 | - @Param("entityId") UUID entityId, | ||
85 | - @Param("status") Set<AlarmStatus> status); | ||
86 | - | 89 | + @Param("affectedEntityId") UUID affectedEntityId, |
90 | + @Param("affectedEntityType") String affectedEntityType, | ||
91 | + @Param("alarmStatuses") Set<AlarmStatus> alarmStatuses); | ||
87 | } | 92 | } |
@@ -123,7 +123,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A | @@ -123,7 +123,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A | ||
123 | } | 123 | } |
124 | 124 | ||
125 | @Override | 125 | @Override |
126 | - public Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> status) { | ||
127 | - return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), status); | 126 | + public Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> statuses) { |
127 | + return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses); | ||
128 | } | 128 | } |
129 | } | 129 | } |
@@ -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,12 @@ | @@ -15,7 +15,12 @@ | ||
15 | /// | 15 | /// |
16 | 16 | ||
17 | import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; | 17 | import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; |
18 | -import { AggregationType } from '@shared/models/time/time.models'; | 18 | +import { |
19 | + AggregationType, | ||
20 | + calculateIntervalEndTime, | ||
21 | + calculateIntervalStartTime, getCurrentTime, | ||
22 | + QuickTimeInterval, SubscriptionTimewindow | ||
23 | +} from '@shared/models/time/time.models'; | ||
19 | import { UtilsService } from '@core/services/utils.service'; | 24 | import { UtilsService } from '@core/services/utils.service'; |
20 | import { deepClone } from '@core/utils'; | 25 | import { deepClone } from '@core/utils'; |
21 | import Timeout = NodeJS.Timeout; | 26 | import Timeout = NodeJS.Timeout; |
@@ -73,33 +78,29 @@ export class DataAggregator { | @@ -73,33 +78,29 @@ export class DataAggregator { | ||
73 | private resetPending = false; | 78 | private resetPending = false; |
74 | private updatedData = false; | 79 | private updatedData = false; |
75 | 80 | ||
76 | - private noAggregation = this.aggregationType === AggregationType.NONE; | ||
77 | - private aggregationTimeout = Math.max(this.interval, 1000); | 81 | + private noAggregation = this.subsTw.aggregation.type === AggregationType.NONE; |
82 | + private aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); | ||
78 | private readonly aggFunction: AggFunction; | 83 | private readonly aggFunction: AggFunction; |
79 | 84 | ||
80 | private intervalTimeoutHandle: Timeout; | 85 | private intervalTimeoutHandle: Timeout; |
81 | private intervalScheduledTime: number; | 86 | private intervalScheduledTime: number; |
82 | 87 | ||
88 | + private startTs = this.subsTw.startTs + this.subsTw.tsOffset; | ||
83 | private endTs: number; | 89 | private endTs: number; |
84 | private elapsed: number; | 90 | private elapsed: number; |
85 | 91 | ||
86 | constructor(private onDataCb: onAggregatedData, | 92 | constructor(private onDataCb: onAggregatedData, |
87 | private tsKeyNames: string[], | 93 | private tsKeyNames: string[], |
88 | - private startTs: number, | ||
89 | - private limit: number, | ||
90 | - private aggregationType: AggregationType, | ||
91 | - private timeWindow: number, | ||
92 | - private interval: number, | ||
93 | - private stateData: boolean, | 94 | + private subsTw: SubscriptionTimewindow, |
94 | private utils: UtilsService, | 95 | private utils: UtilsService, |
95 | private ignoreDataUpdateOnIntervalTick: boolean) { | 96 | private ignoreDataUpdateOnIntervalTick: boolean) { |
96 | this.tsKeyNames.forEach((key) => { | 97 | this.tsKeyNames.forEach((key) => { |
97 | this.dataBuffer[key] = []; | 98 | this.dataBuffer[key] = []; |
98 | }); | 99 | }); |
99 | - if (this.stateData) { | 100 | + if (this.subsTw.aggregation.stateData) { |
100 | this.lastPrevKvPairData = {}; | 101 | this.lastPrevKvPairData = {}; |
101 | } | 102 | } |
102 | - switch (this.aggregationType) { | 103 | + switch (this.subsTw.aggregation.type) { |
103 | case AggregationType.MIN: | 104 | case AggregationType.MIN: |
104 | this.aggFunction = min; | 105 | this.aggFunction = min; |
105 | break; | 106 | break; |
@@ -129,18 +130,21 @@ export class DataAggregator { | @@ -129,18 +130,21 @@ export class DataAggregator { | ||
129 | return prevOnDataCb; | 130 | return prevOnDataCb; |
130 | } | 131 | } |
131 | 132 | ||
132 | - public reset(startTs: number, timeWindow: number, interval: number) { | 133 | + public reset(subsTw: SubscriptionTimewindow) { |
133 | if (this.intervalTimeoutHandle) { | 134 | if (this.intervalTimeoutHandle) { |
134 | clearTimeout(this.intervalTimeoutHandle); | 135 | clearTimeout(this.intervalTimeoutHandle); |
135 | this.intervalTimeoutHandle = null; | 136 | this.intervalTimeoutHandle = null; |
136 | } | 137 | } |
138 | + this.subsTw = subsTw; | ||
137 | this.intervalScheduledTime = this.utils.currentPerfTime(); | 139 | this.intervalScheduledTime = this.utils.currentPerfTime(); |
138 | - this.startTs = startTs; | ||
139 | - this.timeWindow = timeWindow; | ||
140 | - this.interval = interval; | ||
141 | - this.endTs = this.startTs + this.timeWindow; | 140 | + this.startTs = this.subsTw.startTs + this.subsTw.tsOffset; |
141 | + if (this.subsTw.quickInterval) { | ||
142 | + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset; | ||
143 | + } else { | ||
144 | + this.endTs = this.startTs + this.subsTw.aggregation.timeWindow; | ||
145 | + } | ||
142 | this.elapsed = 0; | 146 | this.elapsed = 0; |
143 | - this.aggregationTimeout = Math.max(this.interval, 1000); | 147 | + this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); |
144 | this.resetPending = true; | 148 | this.resetPending = true; |
145 | this.updatedData = false; | 149 | this.updatedData = false; |
146 | this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); | 150 | this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); |
@@ -161,7 +165,11 @@ export class DataAggregator { | @@ -161,7 +165,11 @@ export class DataAggregator { | ||
161 | if (!this.dataReceived) { | 165 | if (!this.dataReceived) { |
162 | this.elapsed = 0; | 166 | this.elapsed = 0; |
163 | this.dataReceived = true; | 167 | this.dataReceived = true; |
164 | - this.endTs = this.startTs + this.timeWindow; | 168 | + if (this.subsTw.quickInterval) { |
169 | + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset; | ||
170 | + } else { | ||
171 | + this.endTs = this.startTs + this.subsTw.aggregation.timeWindow; | ||
172 | + } | ||
165 | } | 173 | } |
166 | if (this.resetPending) { | 174 | if (this.resetPending) { |
167 | this.resetPending = false; | 175 | this.resetPending = false; |
@@ -195,12 +203,19 @@ export class DataAggregator { | @@ -195,12 +203,19 @@ export class DataAggregator { | ||
195 | this.intervalTimeoutHandle = null; | 203 | this.intervalTimeoutHandle = null; |
196 | } | 204 | } |
197 | if (!history) { | 205 | if (!history) { |
198 | - const delta = Math.floor(this.elapsed / this.interval); | 206 | + const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval); |
199 | if (delta || !this.data) { | 207 | if (delta || !this.data) { |
200 | - this.startTs += delta * this.interval; | ||
201 | - this.endTs += delta * this.interval; | 208 | + const tickTs = delta * this.subsTw.aggregation.interval; |
209 | + if (this.subsTw.quickInterval) { | ||
210 | + const currentDate = getCurrentTime(this.subsTw.timezone); | ||
211 | + this.startTs = calculateIntervalStartTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; | ||
212 | + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset; | ||
213 | + } else { | ||
214 | + this.startTs += tickTs; | ||
215 | + this.endTs += tickTs; | ||
216 | + } | ||
202 | this.data = this.updateData(); | 217 | this.data = this.updateData(); |
203 | - this.elapsed = this.elapsed - delta * this.interval; | 218 | + this.elapsed = this.elapsed - delta * this.subsTw.aggregation.interval; |
204 | } | 219 | } |
205 | } else { | 220 | } else { |
206 | this.data = this.updateData(); | 221 | this.data = this.updateData(); |
@@ -223,7 +238,7 @@ export class DataAggregator { | @@ -223,7 +238,7 @@ export class DataAggregator { | ||
223 | let keyData = this.dataBuffer[key]; | 238 | let keyData = this.dataBuffer[key]; |
224 | aggKeyData.forEach((aggData, aggTimestamp) => { | 239 | aggKeyData.forEach((aggData, aggTimestamp) => { |
225 | if (aggTimestamp <= this.startTs) { | 240 | if (aggTimestamp <= this.startTs) { |
226 | - if (this.stateData && | 241 | + if (this.subsTw.aggregation.stateData && |
227 | (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { | 242 | (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { |
228 | this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; | 243 | this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; |
229 | } | 244 | } |
@@ -235,11 +250,11 @@ export class DataAggregator { | @@ -235,11 +250,11 @@ export class DataAggregator { | ||
235 | } | 250 | } |
236 | }); | 251 | }); |
237 | keyData.sort((set1, set2) => set1[0] - set2[0]); | 252 | keyData.sort((set1, set2) => set1[0] - set2[0]); |
238 | - if (this.stateData) { | 253 | + if (this.subsTw.aggregation.stateData) { |
239 | this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key])); | 254 | this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key])); |
240 | } | 255 | } |
241 | - if (keyData.length > this.limit) { | ||
242 | - keyData = keyData.slice(keyData.length - this.limit); | 256 | + if (keyData.length > this.subsTw.aggregation.limit) { |
257 | + keyData = keyData.slice(keyData.length - this.subsTw.aggregation.limit); | ||
243 | } | 258 | } |
244 | this.dataBuffer[key] = keyData; | 259 | this.dataBuffer[key] = keyData; |
245 | } | 260 | } |
@@ -275,7 +290,7 @@ export class DataAggregator { | @@ -275,7 +290,7 @@ export class DataAggregator { | ||
275 | } | 290 | } |
276 | 291 | ||
277 | private processAggregatedData(data: SubscriptionData): AggregationMap { | 292 | private processAggregatedData(data: SubscriptionData): AggregationMap { |
278 | - const isCount = this.aggregationType === AggregationType.COUNT; | 293 | + const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; |
279 | const aggregationMap: AggregationMap = {}; | 294 | const aggregationMap: AggregationMap = {}; |
280 | for (const key of Object.keys(data)) { | 295 | for (const key of Object.keys(data)) { |
281 | let aggKeyData = aggregationMap[key]; | 296 | let aggKeyData = aggregationMap[key]; |
@@ -300,7 +315,7 @@ export class DataAggregator { | @@ -300,7 +315,7 @@ export class DataAggregator { | ||
300 | } | 315 | } |
301 | 316 | ||
302 | private updateAggregatedData(data: SubscriptionData) { | 317 | private updateAggregatedData(data: SubscriptionData) { |
303 | - const isCount = this.aggregationType === AggregationType.COUNT; | 318 | + const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; |
304 | for (const key of Object.keys(data)) { | 319 | for (const key of Object.keys(data)) { |
305 | let aggKeyData = this.aggregationMap[key]; | 320 | let aggKeyData = this.aggregationMap[key]; |
306 | if (!aggKeyData) { | 321 | if (!aggKeyData) { |
@@ -312,7 +327,8 @@ export class DataAggregator { | @@ -312,7 +327,8 @@ export class DataAggregator { | ||
312 | const timestamp = kvPair[0]; | 327 | const timestamp = kvPair[0]; |
313 | const value = this.convertValue(kvPair[1]); | 328 | const value = this.convertValue(kvPair[1]); |
314 | const aggTimestamp = this.noAggregation ? timestamp : (this.startTs + | 329 | const aggTimestamp = this.noAggregation ? timestamp : (this.startTs + |
315 | - Math.floor((timestamp - this.startTs) / this.interval) * this.interval + this.interval / 2); | 330 | + Math.floor((timestamp - this.startTs) / this.subsTw.aggregation.interval) * |
331 | + this.subsTw.aggregation.interval + this.subsTw.aggregation.interval / 2); | ||
316 | let aggData = aggKeyData.get(aggTimestamp); | 332 | let aggData = aggKeyData.get(aggTimestamp); |
317 | if (!aggData) { | 333 | if (!aggData) { |
318 | aggData = { | 334 | aggData = { |
@@ -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 && |
@@ -238,6 +241,11 @@ export class EntityDataSubscription { | @@ -238,6 +241,11 @@ export class EntityDataSubscription { | ||
238 | 241 | ||
239 | if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { | 242 | if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { |
240 | this.prepareSubscriptionCommands(this.dataCommand); | 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); |
@@ -256,8 +264,8 @@ export class EntityDataSubscription { | @@ -256,8 +264,8 @@ export class EntityDataSubscription { | ||
256 | if (this.started) { | 264 | if (this.started) { |
257 | const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand; | 265 | const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand; |
258 | if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && | 266 | if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && |
259 | - !this.history && this.tsFields.length) { | ||
260 | - const newSubsTw: SubscriptionTimewindow = this.listener.updateRealtimeSubscription(); | 267 | + !this.history && this.tsFields.length) { |
268 | + const newSubsTw = this.listener.updateRealtimeSubscription(); | ||
261 | this.subsTw = newSubsTw; | 269 | this.subsTw = newSubsTw; |
262 | targetCommand.tsCmd.startTs = this.subsTw.startTs; | 270 | targetCommand.tsCmd.startTs = this.subsTw.startTs; |
263 | targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow; | 271 | targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow; |
@@ -266,18 +274,25 @@ export class EntityDataSubscription { | @@ -266,18 +274,25 @@ export class EntityDataSubscription { | ||
266 | targetCommand.tsCmd.agg = this.subsTw.aggregation.type; | 274 | targetCommand.tsCmd.agg = this.subsTw.aggregation.type; |
267 | targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData; | 275 | targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData; |
268 | this.dataAggregators.forEach((dataAggregator) => { | 276 | this.dataAggregators.forEach((dataAggregator) => { |
269 | - dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); | 277 | + dataAggregator.reset(newSubsTw); |
270 | }); | 278 | }); |
271 | } | 279 | } |
280 | + this.subscriber.setTsOffset(this.subsTw.tsOffset); | ||
272 | targetCommand.query = this.dataCommand.query; | 281 | targetCommand.query = this.dataCommand.query; |
273 | this.subscriber.subscriptionCommands = [targetCommand]; | 282 | this.subscriber.subscriptionCommands = [targetCommand]; |
274 | } else { | 283 | } else { |
275 | this.subscriber.subscriptionCommands = [this.dataCommand]; | 284 | this.subscriber.subscriptionCommands = [this.dataCommand]; |
276 | } | 285 | } |
277 | }); | 286 | }); |
278 | - | ||
279 | this.subscriber.subscribe(); | 287 | this.subscriber.subscribe(); |
280 | } 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 | + | ||
281 | const entityData: EntityData = { | 296 | const entityData: EntityData = { |
282 | entityId: { | 297 | entityId: { |
283 | id: NULL_UUID, | 298 | id: NULL_UUID, |
@@ -288,7 +303,7 @@ export class EntityDataSubscription { | @@ -288,7 +303,7 @@ export class EntityDataSubscription { | ||
288 | }; | 303 | }; |
289 | const name = DatasourceType.function; | 304 | const name = DatasourceType.function; |
290 | entityData.latest[EntityKeyType.ENTITY_FIELD] = { | 305 | entityData.latest[EntityKeyType.ENTITY_FIELD] = { |
291 | - name: {ts: Date.now(), value: name} | 306 | + name: {ts: Date.now() + tsOffset, value: name} |
292 | }; | 307 | }; |
293 | const pageData: PageData<EntityData> = { | 308 | const pageData: PageData<EntityData> = { |
294 | data: [entityData], | 309 | data: [entityData], |
@@ -298,7 +313,9 @@ export class EntityDataSubscription { | @@ -298,7 +313,9 @@ export class EntityDataSubscription { | ||
298 | }; | 313 | }; |
299 | this.onPageData(pageData); | 314 | this.onPageData(pageData); |
300 | } else if (this.datasourceType === DatasourceType.entityCount) { | 315 | } else if (this.datasourceType === DatasourceType.entityCount) { |
316 | + this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; | ||
301 | this.subscriber = new TelemetrySubscriber(this.telemetryService); | 317 | this.subscriber = new TelemetrySubscriber(this.telemetryService); |
318 | + this.subscriber.setTsOffset(this.latestTsOffset); | ||
302 | this.countCommand = new EntityCountCmd(); | 319 | this.countCommand = new EntityCountCmd(); |
303 | let keyFilters = this.entityDataSubscriptionOptions.keyFilters; | 320 | let keyFilters = this.entityDataSubscriptionOptions.keyFilters; |
304 | if (this.entityDataSubscriptionOptions.additionalKeyFilters) { | 321 | if (this.entityDataSubscriptionOptions.additionalKeyFilters) { |
@@ -331,13 +348,13 @@ export class EntityDataSubscription { | @@ -331,13 +348,13 @@ export class EntityDataSubscription { | ||
331 | latest: { | 348 | latest: { |
332 | [EntityKeyType.ENTITY_FIELD]: { | 349 | [EntityKeyType.ENTITY_FIELD]: { |
333 | name: { | 350 | name: { |
334 | - ts: Date.now(), | 351 | + ts: Date.now() + this.latestTsOffset, |
335 | value: DatasourceType.entityCount | 352 | value: DatasourceType.entityCount |
336 | } | 353 | } |
337 | }, | 354 | }, |
338 | [EntityKeyType.COUNT]: { | 355 | [EntityKeyType.COUNT]: { |
339 | [countKey.name]: { | 356 | [countKey.name]: { |
340 | - ts: Date.now(), | 357 | + ts: Date.now() + this.latestTsOffset, |
341 | value: entityCountUpdate.count + '' | 358 | value: entityCountUpdate.count + '' |
342 | } | 359 | } |
343 | } | 360 | } |
@@ -358,7 +375,7 @@ export class EntityDataSubscription { | @@ -358,7 +375,7 @@ export class EntityDataSubscription { | ||
358 | latest: { | 375 | latest: { |
359 | [EntityKeyType.COUNT]: { | 376 | [EntityKeyType.COUNT]: { |
360 | [countKey.name]: { | 377 | [countKey.name]: { |
361 | - ts: Date.now(), | 378 | + ts: Date.now() + this.latestTsOffset, |
362 | value: entityCountUpdate.count + '' | 379 | value: entityCountUpdate.count + '' |
363 | } | 380 | } |
364 | } | 381 | } |
@@ -383,6 +400,7 @@ export class EntityDataSubscription { | @@ -383,6 +400,7 @@ export class EntityDataSubscription { | ||
383 | return; | 400 | return; |
384 | } | 401 | } |
385 | this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; | 402 | this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; |
403 | + this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; | ||
386 | this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && | 404 | this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && |
387 | isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); | 405 | isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); |
388 | this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && | 406 | this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && |
@@ -394,10 +412,26 @@ export class EntityDataSubscription { | @@ -394,10 +412,26 @@ export class EntityDataSubscription { | ||
394 | this.subsCommand = new EntityDataCmd(); | 412 | this.subsCommand = new EntityDataCmd(); |
395 | this.subsCommand.cmdId = this.dataCommand.cmdId; | 413 | this.subsCommand.cmdId = this.dataCommand.cmdId; |
396 | this.prepareSubscriptionCommands(this.subsCommand); | 414 | this.prepareSubscriptionCommands(this.subsCommand); |
397 | - if (!this.subsCommand.isEmpty()) { | 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()) { | ||
398 | this.subscriber.subscriptionCommands = [this.subsCommand]; | 426 | this.subscriber.subscriptionCommands = [this.subsCommand]; |
399 | this.subscriber.update(); | 427 | this.subscriber.update(); |
400 | } | 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 | + } | ||
401 | } else if (this.datasourceType === DatasourceType.function) { | 435 | } else if (this.datasourceType === DatasourceType.function) { |
402 | this.startFunction(); | 436 | this.startFunction(); |
403 | } | 437 | } |
@@ -745,12 +779,7 @@ export class EntityDataSubscription { | @@ -745,12 +779,7 @@ export class EntityDataSubscription { | ||
745 | this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb); | 779 | this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb); |
746 | }, | 780 | }, |
747 | tsKeyNames, | 781 | tsKeyNames, |
748 | - subsTw.startTs, | ||
749 | - subsTw.aggregation.limit, | ||
750 | - subsTw.aggregation.type, | ||
751 | - subsTw.aggregation.timeWindow, | ||
752 | - subsTw.aggregation.interval, | ||
753 | - subsTw.aggregation.stateData, | 782 | + subsTw, |
754 | this.utils, | 783 | this.utils, |
755 | this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick | 784 | this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick |
756 | ); | 785 | ); |
@@ -786,7 +815,7 @@ export class EntityDataSubscription { | @@ -786,7 +815,7 @@ export class EntityDataSubscription { | ||
786 | } else { | 815 | } else { |
787 | prevSeries = [0, 0]; | 816 | prevSeries = [0, 0]; |
788 | } | 817 | } |
789 | - const time = Date.now(); | 818 | + const time = Date.now() + this.latestTsOffset; |
790 | const value = dataKey.func(time, prevSeries[1]); | 819 | const value = dataKey.func(time, prevSeries[1]); |
791 | const series: [number, any] = [time, value]; | 820 | const series: [number, any] = [time, value]; |
792 | this.datasourceData[0][dataKey.key].data = [series]; | 821 | this.datasourceData[0][dataKey.key].data = [series]; |
@@ -827,7 +856,8 @@ export class EntityDataSubscription { | @@ -827,7 +856,8 @@ export class EntityDataSubscription { | ||
827 | startTime = dataKey.lastUpdateTime + this.frequency; | 856 | startTime = dataKey.lastUpdateTime + this.frequency; |
828 | endTime = dataKey.lastUpdateTime + deltaElapsed; | 857 | endTime = dataKey.lastUpdateTime + deltaElapsed; |
829 | } else { | 858 | } else { |
830 | - startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs; | 859 | + startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs + |
860 | + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; | ||
831 | endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; | 861 | endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; |
832 | if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { | 862 | if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { |
833 | const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit; | 863 | const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit; |
@@ -835,8 +865,14 @@ export class EntityDataSubscription { | @@ -835,8 +865,14 @@ export class EntityDataSubscription { | ||
835 | } | 865 | } |
836 | } | 866 | } |
837 | } else { | 867 | } else { |
838 | - startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs; | ||
839 | - endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs; | 868 | + startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs + |
869 | + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; | ||
870 | + endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs + | ||
871 | + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; | ||
872 | + } | ||
873 | + if (this.entityDataSubscriptionOptions.subscriptionTimewindow.quickInterval) { | ||
874 | + const currentTime = getCurrentTime().valueOf() + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; | ||
875 | + endTime = Math.min(currentTime, endTime); | ||
840 | } | 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); |
@@ -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 | } |
@@ -37,8 +37,12 @@ import { | @@ -37,8 +37,12 @@ import { | ||
37 | } from '@app/shared/models/widget.models'; | 37 | } from '@app/shared/models/widget.models'; |
38 | import { HttpErrorResponse } from '@angular/common/http'; | 38 | import { HttpErrorResponse } from '@angular/common/http'; |
39 | import { | 39 | import { |
40 | + calculateIntervalEndTime, | ||
41 | + calculateIntervalStartTime, | ||
42 | + calculateTsOffset, | ||
40 | createSubscriptionTimewindow, | 43 | createSubscriptionTimewindow, |
41 | createTimewindowForComparison, | 44 | createTimewindowForComparison, |
45 | + getCurrentTime, | ||
42 | SubscriptionTimewindow, | 46 | SubscriptionTimewindow, |
43 | Timewindow, | 47 | Timewindow, |
44 | toHistoryTimewindow, | 48 | toHistoryTimewindow, |
@@ -77,8 +81,10 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -77,8 +81,10 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
77 | timeWindow: WidgetTimewindow; | 81 | timeWindow: WidgetTimewindow; |
78 | originalTimewindow: Timewindow; | 82 | originalTimewindow: Timewindow; |
79 | timeWindowConfig: Timewindow; | 83 | timeWindowConfig: Timewindow; |
84 | + timezone: string; | ||
80 | subscriptionTimewindow: SubscriptionTimewindow; | 85 | subscriptionTimewindow: SubscriptionTimewindow; |
81 | useDashboardTimewindow: boolean; | 86 | useDashboardTimewindow: boolean; |
87 | + tsOffset = 0; | ||
82 | 88 | ||
83 | hasDataPageLink: boolean; | 89 | hasDataPageLink: boolean; |
84 | singleEntity: boolean; | 90 | singleEntity: boolean; |
@@ -211,6 +217,10 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -211,6 +217,10 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
211 | this.timeWindow = {}; | 217 | this.timeWindow = {}; |
212 | this.useDashboardTimewindow = options.useDashboardTimewindow; | 218 | this.useDashboardTimewindow = options.useDashboardTimewindow; |
213 | 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 | + } | ||
214 | if (this.useDashboardTimewindow) { | 224 | if (this.useDashboardTimewindow) { |
215 | this.timeWindowConfig = deepClone(options.dashboardTimewindow); | 225 | this.timeWindowConfig = deepClone(options.dashboardTimewindow); |
216 | } else { | 226 | } else { |
@@ -576,11 +586,16 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -576,11 +586,16 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
576 | if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { | 586 | if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { |
577 | this.timeWindowConfig = deepClone(newDashboardTimewindow); | 587 | this.timeWindowConfig = deepClone(newDashboardTimewindow); |
578 | this.update(); | 588 | this.update(); |
579 | - 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(); | ||
580 | } | 596 | } |
581 | } | 597 | } |
582 | } | 598 | } |
583 | - return false; | ||
584 | } | 599 | } |
585 | 600 | ||
586 | updateDataVisibility(index: number): void { | 601 | updateDataVisibility(index: number): void { |
@@ -813,6 +828,7 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -813,6 +828,7 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
813 | configDatasource: datasource, | 828 | configDatasource: datasource, |
814 | configDatasourceIndex: datasourceIndex, | 829 | configDatasourceIndex: datasourceIndex, |
815 | subscriptionTimewindow: this.subscriptionTimewindow, | 830 | subscriptionTimewindow: this.subscriptionTimewindow, |
831 | + latestTsOffset: this.tsOffset, | ||
816 | dataLoaded: (pageData, data1, datasourceIndex1, pageLink1) => { | 832 | dataLoaded: (pageData, data1, datasourceIndex1, pageLink1) => { |
817 | this.dataLoaded(pageData, data1, datasourceIndex1, pageLink1, true); | 833 | this.dataLoaded(pageData, data1, datasourceIndex1, pageLink1, true); |
818 | }, | 834 | }, |
@@ -837,9 +853,11 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -837,9 +853,11 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
837 | if (this.alarmDataListener) { | 853 | if (this.alarmDataListener) { |
838 | this.ctx.alarmDataService.stopSubscription(this.alarmDataListener); | 854 | this.ctx.alarmDataService.stopSubscription(this.alarmDataListener); |
839 | } | 855 | } |
856 | + | ||
840 | if (this.timeWindowConfig) { | 857 | if (this.timeWindowConfig) { |
841 | this.updateRealtimeSubscription(); | 858 | this.updateRealtimeSubscription(); |
842 | } | 859 | } |
860 | + | ||
843 | this.alarmDataListener = { | 861 | this.alarmDataListener = { |
844 | subscriptionTimewindow: this.subscriptionTimewindow, | 862 | subscriptionTimewindow: this.subscriptionTimewindow, |
845 | alarmSource: this.alarmSource, | 863 | alarmSource: this.alarmSource, |
@@ -878,25 +896,28 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -878,25 +896,28 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
878 | } | 896 | } |
879 | 897 | ||
880 | private dataSubscribe() { | 898 | private dataSubscribe() { |
899 | + this.updateDataTimewindow(); | ||
881 | if (!this.hasDataPageLink) { | 900 | if (!this.hasDataPageLink) { |
882 | - if (this.type === widgetType.timeseries && this.timeWindowConfig) { | ||
883 | - this.updateDataTimewindow(); | ||
884 | - if (this.subscriptionTimewindow.fixedWindow) { | 901 | + if (this.type === widgetType.timeseries && this.timeWindowConfig && this.subscriptionTimewindow.fixedWindow) { |
885 | this.onDataUpdated(); | 902 | this.onDataUpdated(); |
886 | - } | ||
887 | } | 903 | } |
888 | const forceUpdate = !this.datasources.length; | 904 | const forceUpdate = !this.datasources.length; |
905 | + const notifyDataLoaded = !this.entityDataListeners.filter((listener) => listener.subscription ? true : false).length; | ||
889 | this.entityDataListeners.forEach((listener) => { | 906 | this.entityDataListeners.forEach((listener) => { |
890 | if (this.comparisonEnabled && listener.configDatasource.isAdditional) { | 907 | if (this.comparisonEnabled && listener.configDatasource.isAdditional) { |
891 | listener.subscriptionTimewindow = this.timewindowForComparison; | 908 | listener.subscriptionTimewindow = this.timewindowForComparison; |
892 | } else { | 909 | } else { |
893 | listener.subscriptionTimewindow = this.subscriptionTimewindow; | 910 | listener.subscriptionTimewindow = this.subscriptionTimewindow; |
911 | + listener.latestTsOffset = this.tsOffset; | ||
894 | } | 912 | } |
895 | this.ctx.entityDataService.startSubscription(listener); | 913 | this.ctx.entityDataService.startSubscription(listener); |
896 | }); | 914 | }); |
897 | if (forceUpdate) { | 915 | if (forceUpdate) { |
898 | this.onDataUpdated(); | 916 | this.onDataUpdated(); |
899 | } | 917 | } |
918 | + if (notifyDataLoaded) { | ||
919 | + this.notifyDataLoaded(); | ||
920 | + } | ||
900 | } | 921 | } |
901 | } | 922 | } |
902 | 923 | ||
@@ -1080,15 +1101,33 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -1080,15 +1101,33 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
1080 | 1101 | ||
1081 | private updateTimewindow() { | 1102 | private updateTimewindow() { |
1082 | this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; | 1103 | this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; |
1104 | + this.timeWindow.timezone = this.subscriptionTimewindow.timezone; | ||
1083 | if (this.subscriptionTimewindow.realtimeWindowMs) { | 1105 | if (this.subscriptionTimewindow.realtimeWindowMs) { |
1084 | - this.timeWindow.maxTime = moment().valueOf() + this.timeWindow.stDiff; | ||
1085 | - this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; | 1106 | + if (this.subscriptionTimewindow.quickInterval) { |
1107 | + const currentDate = getCurrentTime(this.subscriptionTimewindow.timezone); | ||
1108 | + this.timeWindow.maxTime = calculateIntervalEndTime( | ||
1109 | + this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset; | ||
1110 | + this.timeWindow.minTime = calculateIntervalStartTime( | ||
1111 | + this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset; | ||
1112 | + } else { | ||
1113 | + this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff; | ||
1114 | + this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; | ||
1115 | + } | ||
1086 | } else if (this.subscriptionTimewindow.fixedWindow) { | 1116 | } else if (this.subscriptionTimewindow.fixedWindow) { |
1087 | - this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs; | ||
1088 | - this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs; | 1117 | + this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs + this.subscriptionTimewindow.tsOffset; |
1118 | + this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs + this.subscriptionTimewindow.tsOffset; | ||
1089 | } | 1119 | } |
1090 | } | 1120 | } |
1091 | 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 | + | ||
1092 | private updateRealtimeSubscription(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow { | 1131 | private updateRealtimeSubscription(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow { |
1093 | if (subscriptionTimewindow) { | 1132 | if (subscriptionTimewindow) { |
1094 | this.subscriptionTimewindow = subscriptionTimewindow; | 1133 | this.subscriptionTimewindow = subscriptionTimewindow; |
@@ -1103,12 +1142,13 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -1103,12 +1142,13 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
1103 | 1142 | ||
1104 | private updateComparisonTimewindow() { | 1143 | private updateComparisonTimewindow() { |
1105 | this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; | 1144 | this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; |
1145 | + this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone; | ||
1106 | if (this.timewindowForComparison.realtimeWindowMs) { | 1146 | if (this.timewindowForComparison.realtimeWindowMs) { |
1107 | this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); | 1147 | this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); |
1108 | - this.comparisonTimeWindow.minTime = this.comparisonTimeWindow.maxTime - this.timewindowForComparison.realtimeWindowMs; | 1148 | + this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf(); |
1109 | } else if (this.timewindowForComparison.fixedWindow) { | 1149 | } else if (this.timewindowForComparison.fixedWindow) { |
1110 | - this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs; | ||
1111 | - this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs; | 1150 | + this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset; |
1151 | + this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset; | ||
1112 | } | 1152 | } |
1113 | } | 1153 | } |
1114 | 1154 | ||
@@ -1335,7 +1375,7 @@ export class WidgetSubscription implements IWidgetSubscription { | @@ -1335,7 +1375,7 @@ export class WidgetSubscription implements IWidgetSubscription { | ||
1335 | this.onDataUpdated(); | 1375 | this.onDataUpdated(); |
1336 | } | 1376 | } |
1337 | 1377 | ||
1338 | - private alarmsUpdated(_updated: Array<AlarmData>, alarms: PageData<AlarmData>) { | 1378 | + private alarmsUpdated(updated: Array<AlarmData>, alarms: PageData<AlarmData>) { |
1339 | this.alarmsLoaded(alarms, 0, 0); | 1379 | this.alarmsLoaded(alarms, 0, 0); |
1340 | } | 1380 | } |
1341 | 1381 |
@@ -91,6 +91,7 @@ | @@ -91,6 +91,7 @@ | ||
91 | direction="left" | 91 | direction="left" |
92 | tooltipPosition="below" | 92 | tooltipPosition="below" |
93 | aggregation="true" | 93 | aggregation="true" |
94 | + timezone="true" | ||
94 | [(ngModel)]="dashboardCtx.dashboardTimewindow"> | 95 | [(ngModel)]="dashboardCtx.dashboardTimewindow"> |
95 | </tb-timewindow> | 96 | </tb-timewindow> |
96 | <tb-filters-edit [fxShow]="!isEdit && displayFilters()" | 97 | <tb-filters-edit [fxShow]="!isEdit && displayFilters()" |
@@ -95,6 +95,7 @@ | @@ -95,6 +95,7 @@ | ||
95 | <tb-timewindow *ngIf="widget.hasTimewindow" | 95 | <tb-timewindow *ngIf="widget.hasTimewindow" |
96 | #timewindowComponent | 96 | #timewindowComponent |
97 | aggregation="{{widget.hasAggregation}}" | 97 | aggregation="{{widget.hasAggregation}}" |
98 | + timezone="true" | ||
98 | [isEdit]="isEdit" | 99 | [isEdit]="isEdit" |
99 | [(ngModel)]="widgetComponent.widget.config.timewindow" | 100 | [(ngModel)]="widgetComponent.widget.config.timewindow" |
100 | (ngModelChange)="widgetComponent.onTimewindowChanged($event)"> | 101 | (ngModelChange)="widgetComponent.onTimewindowChanged($event)"> |
@@ -55,7 +55,13 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models'; | @@ -55,7 +55,13 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models'; | ||
55 | import { DialogService } from '@core/services/dialog.service'; | 55 | import { DialogService } from '@core/services/dialog.service'; |
56 | import { AddEntityDialogComponent } from './add-entity-dialog.component'; | 56 | import { AddEntityDialogComponent } from './add-entity-dialog.component'; |
57 | import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; | 57 | import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; |
58 | -import { HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; | 58 | +import { |
59 | + calculateIntervalEndTime, | ||
60 | + calculateIntervalStartTime, | ||
61 | + getCurrentTime, | ||
62 | + HistoryWindowType, | ||
63 | + Timewindow | ||
64 | +} from '@shared/models/time/time.models'; | ||
59 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; | 65 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; |
60 | import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; | 66 | import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; |
61 | import { isDefined, isUndefined } from '@core/utils'; | 67 | import { isDefined, isUndefined } from '@core/utils'; |
@@ -296,6 +302,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -296,6 +302,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
296 | const currentTime = Date.now(); | 302 | const currentTime = Date.now(); |
297 | timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; | 303 | timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; |
298 | timePageLink.endTime = currentTime; | 304 | timePageLink.endTime = currentTime; |
305 | + } else if (this.timewindow.history.historyType === HistoryWindowType.INTERVAL) { | ||
306 | + const currentDate = getCurrentTime(); | ||
307 | + timePageLink.startTime = calculateIntervalStartTime(this.timewindow.history.quickInterval, currentDate); | ||
308 | + timePageLink.endTime = calculateIntervalEndTime(this.timewindow.history.quickInterval, currentDate); | ||
299 | } else { | 309 | } else { |
300 | timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs; | 310 | timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs; |
301 | timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs; | 311 | timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs; |
@@ -94,11 +94,10 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, | @@ -94,11 +94,10 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, | ||
94 | items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems) | 94 | items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems) |
95 | }); | 95 | }); |
96 | this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { | 96 | this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { |
97 | - getDefaultTimezone().subscribe((defaultTimezone) => { | ||
98 | - this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false}); | ||
99 | - this.updateValidators(type, true); | ||
100 | - this.alarmScheduleForm.updateValueAndValidity(); | ||
101 | - }); | 97 | + const defaultTimezone = getDefaultTimezone(); |
98 | + this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false}); | ||
99 | + this.updateValidators(type, true); | ||
100 | + this.alarmScheduleForm.updateValueAndValidity(); | ||
102 | }); | 101 | }); |
103 | this.alarmScheduleForm.valueChanges.subscribe(() => { | 102 | this.alarmScheduleForm.valueChanges.subscribe(() => { |
104 | this.updateModel(); | 103 | this.updateModel(); |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2021 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<section class="interval-section" fxLayout="row" fxFlex> | ||
19 | + <mat-form-field fxFlex> | ||
20 | + <mat-label translate>timewindow.interval</mat-label> | ||
21 | + <mat-select [disabled]="disabled" [(ngModel)]="modelValue" (ngModelChange)="onIntervalChange()"> | ||
22 | + <mat-option *ngFor="let interval of intervals" [value]="interval"> | ||
23 | + {{ timeIntervalTranslationMap.get(interval) | translate}} | ||
24 | + </mat-option> | ||
25 | + </mat-select> | ||
26 | + </mat-form-field> | ||
27 | +</section> |
1 | +/** | ||
2 | + * Copyright © 2016-2021 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +:host { | ||
18 | + min-width: 364px; | ||
19 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | ||
3 | +/// | ||
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +/// you may not use this file except in compliance with the License. | ||
6 | +/// You may obtain a copy of the License at | ||
7 | +/// | ||
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +/// | ||
10 | +/// Unless required by applicable law or agreed to in writing, software | ||
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +/// See the License for the specific language governing permissions and | ||
14 | +/// limitations under the License. | ||
15 | +/// | ||
16 | + | ||
17 | +import { Component, forwardRef, Input, OnInit } from '@angular/core'; | ||
18 | +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | ||
19 | +import { QuickTimeInterval, QuickTimeIntervalTranslationMap } from '@shared/models/time/time.models'; | ||
20 | + | ||
21 | +@Component({ | ||
22 | + selector: 'tb-quick-time-interval', | ||
23 | + templateUrl: './quick-time-interval.component.html', | ||
24 | + styleUrls: ['./quick-time-interval.component.scss'], | ||
25 | + providers: [ | ||
26 | + { | ||
27 | + provide: NG_VALUE_ACCESSOR, | ||
28 | + useExisting: forwardRef(() => QuickTimeIntervalComponent), | ||
29 | + multi: true | ||
30 | + } | ||
31 | + ] | ||
32 | +}) | ||
33 | +export class QuickTimeIntervalComponent implements OnInit, ControlValueAccessor { | ||
34 | + | ||
35 | + private allIntervals = Object.values(QuickTimeInterval); | ||
36 | + | ||
37 | + modelValue: QuickTimeInterval; | ||
38 | + timeIntervalTranslationMap = QuickTimeIntervalTranslationMap; | ||
39 | + | ||
40 | + rendered = false; | ||
41 | + | ||
42 | + @Input() disabled: boolean; | ||
43 | + | ||
44 | + @Input() onlyCurrentInterval = false; | ||
45 | + | ||
46 | + private propagateChange = (_: any) => {}; | ||
47 | + | ||
48 | + constructor() { | ||
49 | + } | ||
50 | + | ||
51 | + get intervals() { | ||
52 | + if (this.onlyCurrentInterval) { | ||
53 | + return this.allIntervals.filter(interval => interval.startsWith('CURRENT_')); | ||
54 | + } | ||
55 | + return this.allIntervals; | ||
56 | + } | ||
57 | + | ||
58 | + ngOnInit(): void { | ||
59 | + } | ||
60 | + | ||
61 | + registerOnChange(fn: any): void { | ||
62 | + this.propagateChange = fn; | ||
63 | + } | ||
64 | + | ||
65 | + registerOnTouched(fn: any): void { | ||
66 | + } | ||
67 | + | ||
68 | + setDisabledState(isDisabled: boolean): void { | ||
69 | + this.disabled = isDisabled; | ||
70 | + } | ||
71 | + | ||
72 | + writeValue(interval: QuickTimeInterval): void { | ||
73 | + this.modelValue = interval; | ||
74 | + } | ||
75 | + | ||
76 | + onIntervalChange() { | ||
77 | + this.propagateChange(this.modelValue); | ||
78 | + } | ||
79 | +} |
@@ -21,16 +21,43 @@ | @@ -21,16 +21,43 @@ | ||
21 | <mat-tab-group dynamicHeight [ngClass]="{'tb-headless': historyOnly}" | 21 | <mat-tab-group dynamicHeight [ngClass]="{'tb-headless': historyOnly}" |
22 | (selectedIndexChange)="timewindowForm.markAsDirty()" [(selectedIndex)]="timewindow.selectedTab"> | 22 | (selectedIndexChange)="timewindowForm.markAsDirty()" [(selectedIndex)]="timewindow.selectedTab"> |
23 | <mat-tab label="{{ 'timewindow.realtime' | translate }}"> | 23 | <mat-tab label="{{ 'timewindow.realtime' | translate }}"> |
24 | - <div formGroupName="realtime" class="mat-content mat-padding" fxLayout="column"> | ||
25 | - <tb-timeinterval | ||
26 | - [(hideFlag)]="timewindow.hideInterval" | ||
27 | - (hideFlagChange)="onHideIntervalChanged()" | ||
28 | - [isEdit]="isEdit" | ||
29 | - formControlName="timewindowMs" | ||
30 | - predefinedName="timewindow.last" | ||
31 | - [required]="timewindow.selectedTab === timewindowTypes.REALTIME" | ||
32 | - style="padding-top: 8px;"></tb-timeinterval> | ||
33 | - </div> | 24 | + <section fxLayout="row"> |
25 | + <section *ngIf="isEdit" fxLayout="column" style="padding-top: 8px; padding-left: 16px;"> | ||
26 | + <label class="tb-small hide-label" translate>timewindow.hide</label> | ||
27 | + <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval" | ||
28 | + (ngModelChange)="onHideIntervalChanged()"></mat-checkbox> | ||
29 | + </section> | ||
30 | + <section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval"> | ||
31 | + <div formGroupName="realtime" class="mat-content mat-padding" style="padding-top: 8px;"> | ||
32 | + <mat-radio-group formControlName="realtimeType"> | ||
33 | + <mat-radio-button [value]="realtimeTypes.LAST_INTERVAL" color="primary"> | ||
34 | + <section fxLayout="column"> | ||
35 | + <span translate>timewindow.last</span> | ||
36 | + <tb-timeinterval | ||
37 | + formControlName="timewindowMs" | ||
38 | + predefinedName="timewindow.last" | ||
39 | + [fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL" | ||
40 | + [required]="timewindow.selectedTab === timewindowTypes.REALTIME && | ||
41 | + timewindowForm.get('realtime.realtimeType').value === realtimeTypes.LAST_INTERVAL" | ||
42 | + style="padding-top: 8px;"></tb-timeinterval> | ||
43 | + </section> | ||
44 | + </mat-radio-button> | ||
45 | + <mat-radio-button [value]="realtimeTypes.INTERVAL" color="primary"> | ||
46 | + <section fxLayout="column"> | ||
47 | + <span translate>timewindow.interval</span> | ||
48 | + <tb-quick-time-interval | ||
49 | + formControlName="quickInterval" | ||
50 | + onlyCurrentInterval="true" | ||
51 | + [fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" | ||
52 | + [required]="timewindow.selectedTab === timewindowTypes.REALTIME && | ||
53 | + timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" | ||
54 | + style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval> | ||
55 | + </section> | ||
56 | + </mat-radio-button> | ||
57 | + </mat-radio-group> | ||
58 | + </div> | ||
59 | + </section> | ||
60 | + </section> | ||
34 | </mat-tab> | 61 | </mat-tab> |
35 | <mat-tab label="{{ 'timewindow.history' | translate }}"> | 62 | <mat-tab label="{{ 'timewindow.history' | translate }}"> |
36 | <section fxLayout="row"> | 63 | <section fxLayout="row"> |
@@ -65,6 +92,17 @@ | @@ -65,6 +92,17 @@ | ||
65 | style="padding-top: 8px;"></tb-datetime-period> | 92 | style="padding-top: 8px;"></tb-datetime-period> |
66 | </section> | 93 | </section> |
67 | </mat-radio-button> | 94 | </mat-radio-button> |
95 | + <mat-radio-button [value]="historyTypes.INTERVAL" color="primary"> | ||
96 | + <section fxLayout="column"> | ||
97 | + <span translate>timewindow.interval</span> | ||
98 | + <tb-quick-time-interval | ||
99 | + formControlName="quickInterval" | ||
100 | + [fxShow]="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" | ||
101 | + [required]="timewindow.selectedTab === timewindowTypes.HISTORY && | ||
102 | + timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" | ||
103 | + style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval> | ||
104 | + </section> | ||
105 | + </mat-radio-button> | ||
68 | </mat-radio-group> | 106 | </mat-radio-group> |
69 | </div> | 107 | </div> |
70 | </section> | 108 | </section> |
@@ -95,7 +133,7 @@ | @@ -95,7 +133,7 @@ | ||
95 | <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggInterval" | 133 | <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggInterval" |
96 | (ngModelChange)="onHideAggIntervalChanged()"></mat-checkbox> | 134 | (ngModelChange)="onHideAggIntervalChanged()"></mat-checkbox> |
97 | </section> | 135 | </section> |
98 | - <section fxLayout="column" [fxShow]="isEdit || !timewindow.hideAggInterval"> | 136 | + <section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideAggInterval"> |
99 | <div class="limit-slider-container" | 137 | <div class="limit-slider-container" |
100 | fxLayout="row" fxLayoutAlign="start center"> | 138 | fxLayout="row" fxLayoutAlign="start center"> |
101 | <span translate>aggregation.limit</span> | 139 | <span translate>aggregation.limit</span> |
@@ -139,6 +177,17 @@ | @@ -139,6 +177,17 @@ | ||
139 | predefinedName="aggregation.group-interval"> | 177 | predefinedName="aggregation.group-interval"> |
140 | </tb-timeinterval> | 178 | </tb-timeinterval> |
141 | </div> | 179 | </div> |
180 | + <div *ngIf="timezone" class="mat-content mat-padding" fxLayout="row"> | ||
181 | + <section fxLayout="column" [fxShow]="isEdit"> | ||
182 | + <label class="tb-small hide-label" translate>timewindow.hide</label> | ||
183 | + <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideTimezone" | ||
184 | + (ngModelChange)="onHideTimezoneChanged()"></mat-checkbox> | ||
185 | + </section> | ||
186 | + <tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone" | ||
187 | + localBrowserTimezonePlaceholderOnEmpty="true" | ||
188 | + formControlName="timezone"> | ||
189 | + </tb-timezone-select> | ||
190 | + </div> | ||
142 | <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> | 191 | <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> |
143 | <button type="button" | 192 | <button type="button" |
144 | mat-button | 193 | mat-button |
@@ -20,6 +20,8 @@ import { | @@ -20,6 +20,8 @@ import { | ||
20 | AggregationType, | 20 | AggregationType, |
21 | DAY, | 21 | DAY, |
22 | HistoryWindowType, | 22 | HistoryWindowType, |
23 | + quickTimeIntervalPeriod, | ||
24 | + RealtimeWindowType, | ||
23 | Timewindow, | 25 | Timewindow, |
24 | TimewindowType | 26 | TimewindowType |
25 | } from '@shared/models/time/time.models'; | 27 | } from '@shared/models/time/time.models'; |
@@ -36,6 +38,7 @@ export interface TimewindowPanelData { | @@ -36,6 +38,7 @@ export interface TimewindowPanelData { | ||
36 | historyOnly: boolean; | 38 | historyOnly: boolean; |
37 | timewindow: Timewindow; | 39 | timewindow: Timewindow; |
38 | aggregation: boolean; | 40 | aggregation: boolean; |
41 | + timezone: boolean; | ||
39 | isEdit: boolean; | 42 | isEdit: boolean; |
40 | } | 43 | } |
41 | 44 | ||
@@ -50,6 +53,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -50,6 +53,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
50 | 53 | ||
51 | aggregation = false; | 54 | aggregation = false; |
52 | 55 | ||
56 | + timezone = false; | ||
57 | + | ||
53 | isEdit = false; | 58 | isEdit = false; |
54 | 59 | ||
55 | timewindow: Timewindow; | 60 | timewindow: Timewindow; |
@@ -60,6 +65,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -60,6 +65,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
60 | 65 | ||
61 | historyTypes = HistoryWindowType; | 66 | historyTypes = HistoryWindowType; |
62 | 67 | ||
68 | + realtimeTypes = RealtimeWindowType; | ||
69 | + | ||
63 | timewindowTypes = TimewindowType; | 70 | timewindowTypes = TimewindowType; |
64 | 71 | ||
65 | aggregationTypes = AggregationType; | 72 | aggregationTypes = AggregationType; |
@@ -78,6 +85,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -78,6 +85,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
78 | this.historyOnly = data.historyOnly; | 85 | this.historyOnly = data.historyOnly; |
79 | this.timewindow = data.timewindow; | 86 | this.timewindow = data.timewindow; |
80 | this.aggregation = data.aggregation; | 87 | this.aggregation = data.aggregation; |
88 | + this.timezone = data.timezone; | ||
81 | this.isEdit = data.isEdit; | 89 | this.isEdit = data.isEdit; |
82 | } | 90 | } |
83 | 91 | ||
@@ -85,10 +93,16 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -85,10 +93,16 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
85 | const hideInterval = this.timewindow.hideInterval || false; | 93 | const hideInterval = this.timewindow.hideInterval || false; |
86 | const hideAggregation = this.timewindow.hideAggregation || false; | 94 | const hideAggregation = this.timewindow.hideAggregation || false; |
87 | const hideAggInterval = this.timewindow.hideAggInterval || false; | 95 | const hideAggInterval = this.timewindow.hideAggInterval || false; |
96 | + const hideTimezone = this.timewindow.hideTimezone || false; | ||
88 | 97 | ||
89 | this.timewindowForm = this.fb.group({ | 98 | this.timewindowForm = this.fb.group({ |
90 | realtime: this.fb.group( | 99 | realtime: this.fb.group( |
91 | { | 100 | { |
101 | + realtimeType: this.fb.control({ | ||
102 | + value: this.timewindow.realtime && typeof this.timewindow.realtime.realtimeType !== 'undefined' | ||
103 | + ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL, | ||
104 | + disabled: hideInterval | ||
105 | + }), | ||
92 | timewindowMs: [ | 106 | timewindowMs: [ |
93 | this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined' | 107 | this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined' |
94 | ? this.timewindow.realtime.timewindowMs : null | 108 | ? this.timewindow.realtime.timewindowMs : null |
@@ -96,7 +110,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -96,7 +110,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
96 | interval: [ | 110 | interval: [ |
97 | this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined' | 111 | this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined' |
98 | ? this.timewindow.realtime.interval : null | 112 | ? this.timewindow.realtime.interval : null |
99 | - ] | 113 | + ], |
114 | + quickInterval: this.fb.control({ | ||
115 | + value: this.timewindow.realtime && typeof this.timewindow.realtime.quickInterval !== 'undefined' | ||
116 | + ? this.timewindow.realtime.quickInterval : null, | ||
117 | + disabled: hideInterval | ||
118 | + }) | ||
100 | } | 119 | } |
101 | ), | 120 | ), |
102 | history: this.fb.group( | 121 | history: this.fb.group( |
@@ -119,6 +138,11 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -119,6 +138,11 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
119 | value: this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined' | 138 | value: this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined' |
120 | ? this.timewindow.history.fixedTimewindow : null, | 139 | ? this.timewindow.history.fixedTimewindow : null, |
121 | disabled: hideInterval | 140 | disabled: hideInterval |
141 | + }), | ||
142 | + quickInterval: this.fb.control({ | ||
143 | + value: this.timewindow.history && typeof this.timewindow.history.quickInterval !== 'undefined' | ||
144 | + ? this.timewindow.history.quickInterval : null, | ||
145 | + disabled: hideInterval | ||
122 | }) | 146 | }) |
123 | } | 147 | } |
124 | ), | 148 | ), |
@@ -135,21 +159,29 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -135,21 +159,29 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
135 | disabled: hideAggInterval | 159 | disabled: hideAggInterval |
136 | }, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())]) | 160 | }, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())]) |
137 | } | 161 | } |
138 | - ) | 162 | + ), |
163 | + timezone: this.fb.control({ | ||
164 | + value: this.timewindow.timezone !== 'undefined' | ||
165 | + ? this.timewindow.timezone : null, | ||
166 | + disabled: hideTimezone | ||
167 | + }) | ||
139 | }); | 168 | }); |
140 | } | 169 | } |
141 | 170 | ||
142 | update() { | 171 | update() { |
143 | const timewindowFormValue = this.timewindowForm.getRawValue(); | 172 | const timewindowFormValue = this.timewindowForm.getRawValue(); |
144 | this.timewindow.realtime = { | 173 | this.timewindow.realtime = { |
174 | + realtimeType: timewindowFormValue.realtime.realtimeType, | ||
145 | timewindowMs: timewindowFormValue.realtime.timewindowMs, | 175 | timewindowMs: timewindowFormValue.realtime.timewindowMs, |
176 | + quickInterval: timewindowFormValue.realtime.quickInterval, | ||
146 | interval: timewindowFormValue.realtime.interval | 177 | interval: timewindowFormValue.realtime.interval |
147 | }; | 178 | }; |
148 | this.timewindow.history = { | 179 | this.timewindow.history = { |
149 | historyType: timewindowFormValue.history.historyType, | 180 | historyType: timewindowFormValue.history.historyType, |
150 | timewindowMs: timewindowFormValue.history.timewindowMs, | 181 | timewindowMs: timewindowFormValue.history.timewindowMs, |
151 | interval: timewindowFormValue.history.interval, | 182 | interval: timewindowFormValue.history.interval, |
152 | - fixedTimewindow: timewindowFormValue.history.fixedTimewindow | 183 | + fixedTimewindow: timewindowFormValue.history.fixedTimewindow, |
184 | + quickInterval: timewindowFormValue.history.quickInterval, | ||
153 | }; | 185 | }; |
154 | if (this.aggregation) { | 186 | if (this.aggregation) { |
155 | this.timewindow.aggregation = { | 187 | this.timewindow.aggregation = { |
@@ -157,6 +189,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -157,6 +189,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
157 | limit: timewindowFormValue.aggregation.limit | 189 | limit: timewindowFormValue.aggregation.limit |
158 | }; | 190 | }; |
159 | } | 191 | } |
192 | + if (this.timezone) { | ||
193 | + this.timewindow.timezone = timewindowFormValue.timezone; | ||
194 | + } | ||
160 | this.result = this.timewindow; | 195 | this.result = this.timewindow; |
161 | this.overlayRef.dispose(); | 196 | this.overlayRef.dispose(); |
162 | } | 197 | } |
@@ -174,11 +209,23 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -174,11 +209,23 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
174 | } | 209 | } |
175 | 210 | ||
176 | minRealtimeAggInterval() { | 211 | minRealtimeAggInterval() { |
177 | - return this.timeService.minIntervalLimit(this.timewindowForm.get('realtime.timewindowMs').value); | 212 | + return this.timeService.minIntervalLimit(this.currentRealtimeTimewindow()); |
178 | } | 213 | } |
179 | 214 | ||
180 | maxRealtimeAggInterval() { | 215 | maxRealtimeAggInterval() { |
181 | - return this.timeService.maxIntervalLimit(this.timewindowForm.get('realtime.timewindowMs').value); | 216 | + return this.timeService.maxIntervalLimit(this.currentRealtimeTimewindow()); |
217 | + } | ||
218 | + | ||
219 | + currentRealtimeTimewindow(): number { | ||
220 | + const timeWindowFormValue = this.timewindowForm.getRawValue(); | ||
221 | + switch (timeWindowFormValue.realtime.realtimeType) { | ||
222 | + case RealtimeWindowType.LAST_INTERVAL: | ||
223 | + return timeWindowFormValue.realtime.timewindowMs; | ||
224 | + case RealtimeWindowType.INTERVAL: | ||
225 | + return quickTimeIntervalPeriod(timeWindowFormValue.realtime.quickInterval); | ||
226 | + default: | ||
227 | + return DAY; | ||
228 | + } | ||
182 | } | 229 | } |
183 | 230 | ||
184 | minHistoryAggInterval() { | 231 | minHistoryAggInterval() { |
@@ -193,6 +240,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -193,6 +240,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
193 | const timewindowFormValue = this.timewindowForm.getRawValue(); | 240 | const timewindowFormValue = this.timewindowForm.getRawValue(); |
194 | if (timewindowFormValue.history.historyType === HistoryWindowType.LAST_INTERVAL) { | 241 | if (timewindowFormValue.history.historyType === HistoryWindowType.LAST_INTERVAL) { |
195 | return timewindowFormValue.history.timewindowMs; | 242 | return timewindowFormValue.history.timewindowMs; |
243 | + } else if (timewindowFormValue.history.historyType === HistoryWindowType.INTERVAL) { | ||
244 | + return quickTimeIntervalPeriod(timewindowFormValue.history.quickInterval); | ||
196 | } else if (timewindowFormValue.history.fixedTimewindow) { | 245 | } else if (timewindowFormValue.history.fixedTimewindow) { |
197 | return timewindowFormValue.history.fixedTimewindow.endTimeMs - | 246 | return timewindowFormValue.history.fixedTimewindow.endTimeMs - |
198 | timewindowFormValue.history.fixedTimewindow.startTimeMs; | 247 | timewindowFormValue.history.fixedTimewindow.startTimeMs; |
@@ -206,10 +255,18 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -206,10 +255,18 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
206 | this.timewindowForm.get('history.historyType').disable({emitEvent: false}); | 255 | this.timewindowForm.get('history.historyType').disable({emitEvent: false}); |
207 | this.timewindowForm.get('history.timewindowMs').disable({emitEvent: false}); | 256 | this.timewindowForm.get('history.timewindowMs').disable({emitEvent: false}); |
208 | this.timewindowForm.get('history.fixedTimewindow').disable({emitEvent: false}); | 257 | this.timewindowForm.get('history.fixedTimewindow').disable({emitEvent: false}); |
258 | + this.timewindowForm.get('history.quickInterval').disable({emitEvent: false}); | ||
259 | + this.timewindowForm.get('realtime.realtimeType').disable({emitEvent: false}); | ||
260 | + this.timewindowForm.get('realtime.timewindowMs').disable({emitEvent: false}); | ||
261 | + this.timewindowForm.get('realtime.quickInterval').disable({emitEvent: false}); | ||
209 | } else { | 262 | } else { |
210 | this.timewindowForm.get('history.historyType').enable({emitEvent: false}); | 263 | this.timewindowForm.get('history.historyType').enable({emitEvent: false}); |
211 | this.timewindowForm.get('history.timewindowMs').enable({emitEvent: false}); | 264 | this.timewindowForm.get('history.timewindowMs').enable({emitEvent: false}); |
212 | this.timewindowForm.get('history.fixedTimewindow').enable({emitEvent: false}); | 265 | this.timewindowForm.get('history.fixedTimewindow').enable({emitEvent: false}); |
266 | + this.timewindowForm.get('history.quickInterval').enable({emitEvent: false}); | ||
267 | + this.timewindowForm.get('realtime.realtimeType').enable({emitEvent: false}); | ||
268 | + this.timewindowForm.get('realtime.timewindowMs').enable({emitEvent: false}); | ||
269 | + this.timewindowForm.get('realtime.quickInterval').enable({emitEvent: false}); | ||
213 | } | 270 | } |
214 | this.timewindowForm.markAsDirty(); | 271 | this.timewindowForm.markAsDirty(); |
215 | } | 272 | } |
@@ -232,4 +289,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | @@ -232,4 +289,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { | ||
232 | this.timewindowForm.markAsDirty(); | 289 | this.timewindowForm.markAsDirty(); |
233 | } | 290 | } |
234 | 291 | ||
292 | + onHideTimezoneChanged() { | ||
293 | + if (this.timewindow.hideTimezone) { | ||
294 | + this.timewindowForm.get('timezone').disable({emitEvent: false}); | ||
295 | + } else { | ||
296 | + this.timewindowForm.get('timezone').enable({emitEvent: false}); | ||
297 | + } | ||
298 | + this.timewindowForm.markAsDirty(); | ||
299 | + } | ||
300 | + | ||
235 | } | 301 | } |
@@ -34,7 +34,7 @@ | @@ -34,7 +34,7 @@ | ||
34 | (click)="openEditMode()" | 34 | (click)="openEditMode()" |
35 | matTooltip="{{ 'timewindow.edit' | translate }}" | 35 | matTooltip="{{ 'timewindow.edit' | translate }}" |
36 | [matTooltipPosition]="tooltipPosition"> | 36 | [matTooltipPosition]="tooltipPosition"> |
37 | - {{innerValue?.displayValue}} | 37 | + {{innerValue?.displayValue}} <span [fxShow]="innerValue?.displayTimezoneAbbr !== ''">| <span class="timezone-abbr">{{innerValue.displayTimezoneAbbr}}</span></span> |
38 | </span> | 38 | </span> |
39 | <button *ngIf="direction === 'right'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32" | 39 | <button *ngIf="direction === 'right'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32" |
40 | type="button" | 40 | type="button" |
@@ -31,8 +31,11 @@ import { TranslateService } from '@ngx-translate/core'; | @@ -31,8 +31,11 @@ import { TranslateService } from '@ngx-translate/core'; | ||
31 | import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; | 31 | import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; |
32 | import { | 32 | import { |
33 | cloneSelectedTimewindow, | 33 | cloneSelectedTimewindow, |
34 | + getTimezoneInfo, | ||
34 | HistoryWindowType, | 35 | HistoryWindowType, |
35 | initModelFromDefaultTimewindow, | 36 | initModelFromDefaultTimewindow, |
37 | + QuickTimeIntervalTranslationMap, | ||
38 | + RealtimeWindowType, | ||
36 | Timewindow, | 39 | Timewindow, |
37 | TimewindowType | 40 | TimewindowType |
38 | } from '@shared/models/time/time.models'; | 41 | } from '@shared/models/time/time.models'; |
@@ -49,7 +52,7 @@ import { BreakpointObserver } from '@angular/cdk/layout'; | @@ -49,7 +52,7 @@ import { BreakpointObserver } from '@angular/cdk/layout'; | ||
49 | import { WINDOW } from '@core/services/window.service'; | 52 | import { WINDOW } from '@core/services/window.service'; |
50 | import { TimeService } from '@core/services/time.service'; | 53 | import { TimeService } from '@core/services/time.service'; |
51 | import { TooltipPosition } from '@angular/material/tooltip'; | 54 | import { TooltipPosition } from '@angular/material/tooltip'; |
52 | -import { deepClone } from '@core/utils'; | 55 | +import { deepClone, isDefinedAndNotNull } from '@core/utils'; |
53 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; | 56 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
54 | 57 | ||
55 | // @dynamic | 58 | // @dynamic |
@@ -89,6 +92,17 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces | @@ -89,6 +92,17 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces | ||
89 | return this.aggregationValue; | 92 | return this.aggregationValue; |
90 | } | 93 | } |
91 | 94 | ||
95 | + timezoneValue = false; | ||
96 | + | ||
97 | + @Input() | ||
98 | + set timezone(val) { | ||
99 | + this.timezoneValue = coerceBooleanProperty(val); | ||
100 | + } | ||
101 | + | ||
102 | + get timezone() { | ||
103 | + return this.timezoneValue; | ||
104 | + } | ||
105 | + | ||
92 | isToolbarValue = false; | 106 | isToolbarValue = false; |
93 | 107 | ||
94 | @Input() | 108 | @Input() |
@@ -169,7 +183,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces | @@ -169,7 +183,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces | ||
169 | }); | 183 | }); |
170 | if (isGtXs) { | 184 | if (isGtXs) { |
171 | config.minWidth = '417px'; | 185 | config.minWidth = '417px'; |
172 | - config.maxHeight = '440px'; | 186 | + config.maxHeight = '500px'; |
173 | const panelHeight = 375; | 187 | const panelHeight = 375; |
174 | const panelWidth = 417; | 188 | const panelWidth = 417; |
175 | const el = this.timewindowPanelOrigin.elementRef.nativeElement; | 189 | const el = this.timewindowPanelOrigin.elementRef.nativeElement; |
@@ -225,6 +239,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces | @@ -225,6 +239,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces | ||
225 | timewindow: deepClone(this.innerValue), | 239 | timewindow: deepClone(this.innerValue), |
226 | historyOnly: this.historyOnly, | 240 | historyOnly: this.historyOnly, |
227 | aggregation: this.aggregation, | 241 | aggregation: this.aggregation, |
242 | + timezone: this.timezone, | ||
228 | isEdit: this.isEdit | 243 | isEdit: this.isEdit |
229 | } | 244 | } |
230 | ); | 245 | ); |
@@ -272,20 +287,32 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces | @@ -272,20 +287,32 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces | ||
272 | 287 | ||
273 | updateDisplayValue() { | 288 | updateDisplayValue() { |
274 | if (this.innerValue.selectedTab === TimewindowType.REALTIME && !this.historyOnly) { | 289 | if (this.innerValue.selectedTab === TimewindowType.REALTIME && !this.historyOnly) { |
275 | - this.innerValue.displayValue = this.translate.instant('timewindow.realtime') + ' - ' + | ||
276 | - this.translate.instant('timewindow.last-prefix') + ' ' + | ||
277 | - this.millisecondsToTimeStringPipe.transform(this.innerValue.realtime.timewindowMs); | 290 | + this.innerValue.displayValue = this.translate.instant('timewindow.realtime') + ' - '; |
291 | + if (this.innerValue.realtime.realtimeType === RealtimeWindowType.INTERVAL) { | ||
292 | + this.innerValue.displayValue += this.translate.instant(QuickTimeIntervalTranslationMap.get(this.innerValue.realtime.quickInterval)); | ||
293 | + } else { | ||
294 | + this.innerValue.displayValue += this.translate.instant('timewindow.last-prefix') + ' ' + | ||
295 | + this.millisecondsToTimeStringPipe.transform(this.innerValue.realtime.timewindowMs); | ||
296 | + } | ||
278 | } else { | 297 | } else { |
279 | this.innerValue.displayValue = !this.historyOnly ? (this.translate.instant('timewindow.history') + ' - ') : ''; | 298 | this.innerValue.displayValue = !this.historyOnly ? (this.translate.instant('timewindow.history') + ' - ') : ''; |
280 | if (this.innerValue.history.historyType === HistoryWindowType.LAST_INTERVAL) { | 299 | if (this.innerValue.history.historyType === HistoryWindowType.LAST_INTERVAL) { |
281 | this.innerValue.displayValue += this.translate.instant('timewindow.last-prefix') + ' ' + | 300 | this.innerValue.displayValue += this.translate.instant('timewindow.last-prefix') + ' ' + |
282 | this.millisecondsToTimeStringPipe.transform(this.innerValue.history.timewindowMs); | 301 | this.millisecondsToTimeStringPipe.transform(this.innerValue.history.timewindowMs); |
302 | + } else if (this.innerValue.history.historyType === HistoryWindowType.INTERVAL) { | ||
303 | + this.innerValue.displayValue += this.translate.instant(QuickTimeIntervalTranslationMap.get(this.innerValue.history.quickInterval)); | ||
283 | } else { | 304 | } else { |
284 | const startString = this.datePipe.transform(this.innerValue.history.fixedTimewindow.startTimeMs, 'yyyy-MM-dd HH:mm:ss'); | 305 | const startString = this.datePipe.transform(this.innerValue.history.fixedTimewindow.startTimeMs, 'yyyy-MM-dd HH:mm:ss'); |
285 | const endString = this.datePipe.transform(this.innerValue.history.fixedTimewindow.endTimeMs, 'yyyy-MM-dd HH:mm:ss'); | 306 | const endString = this.datePipe.transform(this.innerValue.history.fixedTimewindow.endTimeMs, 'yyyy-MM-dd HH:mm:ss'); |
286 | this.innerValue.displayValue += this.translate.instant('timewindow.period', {startTime: startString, endTime: endString}); | 307 | this.innerValue.displayValue += this.translate.instant('timewindow.period', {startTime: startString, endTime: endString}); |
287 | } | 308 | } |
288 | } | 309 | } |
310 | + if (isDefinedAndNotNull(this.innerValue.timezone) && this.innerValue.timezone !== '') { | ||
311 | + this.innerValue.displayValue += ' '; | ||
312 | + this.innerValue.displayTimezoneAbbr = getTimezoneInfo(this.innerValue.timezone).abbr; | ||
313 | + } else { | ||
314 | + this.innerValue.displayTimezoneAbbr = ''; | ||
315 | + } | ||
289 | } | 316 | } |
290 | 317 | ||
291 | hideLabel() { | 318 | hideLabel() { |
@@ -16,14 +16,15 @@ | @@ -16,14 +16,15 @@ | ||
16 | 16 | ||
17 | import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core'; | 17 | import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core'; |
18 | import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; | 18 | import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; |
19 | -import { Observable } from 'rxjs'; | 19 | +import { Observable, of } from 'rxjs'; |
20 | import { map, mergeMap, share, tap } from 'rxjs/operators'; | 20 | import { map, mergeMap, share, tap } from 'rxjs/operators'; |
21 | import { Store } from '@ngrx/store'; | 21 | import { Store } from '@ngrx/store'; |
22 | import { AppState } from '@app/core/core.state'; | 22 | 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', |
@@ -43,10 +44,6 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -43,10 +44,6 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
43 | 44 | ||
44 | defaultTimezoneId: string = null; | 45 | defaultTimezoneId: string = null; |
45 | 46 | ||
46 | - timezones$ = getTimezones().pipe( | ||
47 | - share() | ||
48 | - ); | ||
49 | - | ||
50 | @Input() | 47 | @Input() |
51 | set defaultTimezone(timezone: string) { | 48 | set defaultTimezone(timezone: string) { |
52 | if (this.defaultTimezoneId !== timezone) { | 49 | if (this.defaultTimezoneId !== timezone) { |
@@ -72,6 +69,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -72,6 +69,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
72 | this.userTimezoneByDefaultValue = coerceBooleanProperty(value); | 69 | this.userTimezoneByDefaultValue = coerceBooleanProperty(value); |
73 | } | 70 | } |
74 | 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 | + | ||
75 | @Input() | 81 | @Input() |
76 | disabled: boolean; | 82 | disabled: boolean; |
77 | 83 | ||
@@ -85,6 +91,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -85,6 +91,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
85 | 91 | ||
86 | private dirty = false; | 92 | private dirty = false; |
87 | 93 | ||
94 | + private localBrowserTimezoneInfoPlaceholder: TimezoneInfo; | ||
95 | + | ||
96 | + private timezones: Array<TimezoneInfo>; | ||
97 | + | ||
88 | private propagateChange = (v: any) => { }; | 98 | private propagateChange = (v: any) => { }; |
89 | 99 | ||
90 | constructor(private store: Store<AppState>, | 100 | constructor(private store: Store<AppState>, |
@@ -138,23 +148,24 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -138,23 +148,24 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
138 | 148 | ||
139 | writeValue(value: string | null): void { | 149 | writeValue(value: string | null): void { |
140 | this.searchText = ''; | 150 | this.searchText = ''; |
141 | - getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe( | ||
142 | - (foundTimezone) => { | ||
143 | - if (foundTimezone !== null) { | ||
144 | - this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false}); | ||
145 | - if (foundTimezone.id !== value) { | ||
146 | - setTimeout(() => { | ||
147 | - this.updateView(foundTimezone.id); | ||
148 | - }, 0); | ||
149 | - } else { | ||
150 | - this.modelValue = value; | ||
151 | - } | ||
152 | - } else { | ||
153 | - this.modelValue = null; | ||
154 | - this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false}); | ||
155 | - } | 151 | + const foundTimezone = getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue); |
152 | + if (foundTimezone !== null) { | ||
153 | + this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false}); | ||
154 | + if (foundTimezone.id !== value) { | ||
155 | + setTimeout(() => { | ||
156 | + this.updateView(foundTimezone.id); | ||
157 | + }, 0); | ||
158 | + } else { | ||
159 | + this.modelValue = value; | ||
160 | + } | ||
161 | + } else { | ||
162 | + this.modelValue = null; | ||
163 | + if (this.localBrowserTimezonePlaceholderOnEmptyValue) { | ||
164 | + this.selectTimezoneFormGroup.get('timezone').patchValue(this.getLocalBrowserTimezoneInfoPlaceholder(), {emitEvent: false}); | ||
165 | + } else { | ||
166 | + this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false}); | ||
156 | } | 167 | } |
157 | - ); | 168 | + } |
158 | this.dirty = true; | 169 | this.dirty = true; |
159 | } | 170 | } |
160 | 171 | ||
@@ -169,15 +180,19 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -169,15 +180,19 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
169 | if (this.ignoreClosePanel) { | 180 | if (this.ignoreClosePanel) { |
170 | this.ignoreClosePanel = false; | 181 | this.ignoreClosePanel = false; |
171 | } else { | 182 | } else { |
172 | - if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) { | ||
173 | - getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe( | ||
174 | - (defaultTimezoneInfo) => { | ||
175 | - if (defaultTimezoneInfo !== null) { | ||
176 | - this.ngZone.run(() => { | ||
177 | - this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true}); | ||
178 | - }); | ||
179 | - } | ||
180 | - }); | 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) { | ||
192 | + this.ngZone.run(() => { | ||
193 | + this.selectTimezoneFormGroup.get('timezone').reset(this.getLocalBrowserTimezoneInfoPlaceholder(), {emitEvent: true}); | ||
194 | + }); | ||
195 | + } | ||
181 | } | 196 | } |
182 | } | 197 | } |
183 | } | 198 | } |
@@ -196,12 +211,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -196,12 +211,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
196 | fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { | 211 | fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { |
197 | this.searchText = searchText; | 212 | this.searchText = searchText; |
198 | if (searchText && searchText.length) { | 213 | if (searchText && searchText.length) { |
199 | - return getTimezones().pipe( | ||
200 | - map((timezones) => timezones.filter((timezoneInfo) => | ||
201 | - timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase()))) | ||
202 | - ); | 214 | + return of(this.loadTimezones().filter((timezoneInfo) => |
215 | + timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase()))); | ||
203 | } | 216 | } |
204 | - return getTimezones(); | 217 | + return of(this.loadTimezones()); |
205 | } | 218 | } |
206 | 219 | ||
207 | clear() { | 220 | clear() { |
@@ -211,4 +224,23 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | @@ -211,4 +224,23 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af | ||
211 | }, 0); | 224 | }, 0); |
212 | } | 225 | } |
213 | 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 | + } | ||
214 | } | 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', |
@@ -430,6 +433,44 @@ export class EntityDataUpdate extends DataUpdate<EntityData> { | @@ -430,6 +433,44 @@ export class EntityDataUpdate extends DataUpdate<EntityData> { | ||
430 | constructor(msg: EntityDataUpdateMsg) { | 433 | constructor(msg: EntityDataUpdateMsg) { |
431 | super(msg); | 434 | super(msg); |
432 | } | 435 | } |
436 | + | ||
437 | + public prepareData(tsOffset: number) { | ||
438 | + if (this.data) { | ||
439 | + this.processEntityData(this.data.data, tsOffset); | ||
440 | + } | ||
441 | + if (this.update) { | ||
442 | + this.processEntityData(this.update, tsOffset); | ||
443 | + } | ||
444 | + } | ||
445 | + | ||
446 | + private processEntityData(data: Array<EntityData>, tsOffset: number) { | ||
447 | + for (const entityData of data) { | ||
448 | + if (entityData.timeseries) { | ||
449 | + for (const key of Object.keys(entityData.timeseries)) { | ||
450 | + const tsValues = entityData.timeseries[key]; | ||
451 | + for (const tsValue of tsValues) { | ||
452 | + if (tsValue.ts) { | ||
453 | + tsValue.ts += tsOffset; | ||
454 | + } | ||
455 | + } | ||
456 | + } | ||
457 | + } | ||
458 | + if (entityData.latest) { | ||
459 | + for (const entityKeyType of Object.keys(entityData.latest)) { | ||
460 | + const keyTypeValues = entityData.latest[entityKeyType]; | ||
461 | + for (const key of Object.keys(keyTypeValues)) { | ||
462 | + const tsValue = keyTypeValues[key]; | ||
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 | + } | ||
469 | + } | ||
470 | + } | ||
471 | + } | ||
472 | + } | ||
473 | + } | ||
433 | } | 474 | } |
434 | 475 | ||
435 | export class AlarmDataUpdate extends DataUpdate<AlarmData> { | 476 | export class AlarmDataUpdate extends DataUpdate<AlarmData> { |
@@ -441,6 +482,48 @@ export class AlarmDataUpdate extends DataUpdate<AlarmData> { | @@ -441,6 +482,48 @@ export class AlarmDataUpdate extends DataUpdate<AlarmData> { | ||
441 | this.allowedEntities = msg.allowedEntities; | 482 | this.allowedEntities = msg.allowedEntities; |
442 | this.totalEntities = msg.totalEntities; | 483 | this.totalEntities = msg.totalEntities; |
443 | } | 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 | + } | ||
444 | } | 527 | } |
445 | 528 | ||
446 | export class EntityCountUpdate extends CmdUpdate { | 529 | export class EntityCountUpdate extends CmdUpdate { |
@@ -468,6 +551,8 @@ export class TelemetrySubscriber { | @@ -468,6 +551,8 @@ export class TelemetrySubscriber { | ||
468 | 551 | ||
469 | private zone: NgZone; | 552 | private zone: NgZone; |
470 | 553 | ||
554 | + private tsOffset = undefined; | ||
555 | + | ||
471 | public subscriptionCommands: Array<WebsocketCmd>; | 556 | public subscriptionCommands: Array<WebsocketCmd>; |
472 | 557 | ||
473 | public data$ = this.dataSubject.asObservable(); | 558 | public data$ = this.dataSubject.asObservable(); |
@@ -522,6 +607,16 @@ export class TelemetrySubscriber { | @@ -522,6 +607,16 @@ export class TelemetrySubscriber { | ||
522 | this.reconnectSubject.complete(); | 607 | this.reconnectSubject.complete(); |
523 | } | 608 | } |
524 | 609 | ||
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 | + } | ||
618 | + } | ||
619 | + | ||
525 | public onData(message: SubscriptionUpdate) { | 620 | public onData(message: SubscriptionUpdate) { |
526 | const cmdId = message.subscriptionId; | 621 | const cmdId = message.subscriptionId; |
527 | let keys: string[]; | 622 | let keys: string[]; |
@@ -545,6 +640,9 @@ export class TelemetrySubscriber { | @@ -545,6 +640,9 @@ export class TelemetrySubscriber { | ||
545 | } | 640 | } |
546 | 641 | ||
547 | public onEntityData(message: EntityDataUpdate) { | 642 | public onEntityData(message: EntityDataUpdate) { |
643 | + if (this.tsOffset) { | ||
644 | + message.prepareData(this.tsOffset); | ||
645 | + } | ||
548 | if (this.zone) { | 646 | if (this.zone) { |
549 | this.zone.run( | 647 | this.zone.run( |
550 | () => { | 648 | () => { |
@@ -557,6 +655,9 @@ export class TelemetrySubscriber { | @@ -557,6 +655,9 @@ export class TelemetrySubscriber { | ||
557 | } | 655 | } |
558 | 656 | ||
559 | public onAlarmData(message: AlarmDataUpdate) { | 657 | public onAlarmData(message: AlarmDataUpdate) { |
658 | + if (this.tsOffset) { | ||
659 | + message.prepareData(this.tsOffset); | ||
660 | + } | ||
560 | if (this.zone) { | 661 | if (this.zone) { |
561 | this.zone.run( | 662 | this.zone.run( |
562 | () => { | 663 | () => { |
@@ -17,9 +17,7 @@ | @@ -17,9 +17,7 @@ | ||
17 | import { TimeService } from '@core/services/time.service'; | 17 | import { TimeService } from '@core/services/time.service'; |
18 | import { deepClone, isDefined, isUndefined } from '@app/core/utils'; | 18 | import { deepClone, isDefined, isUndefined } from '@app/core/utils'; |
19 | import * as moment_ from 'moment'; | 19 | import * as moment_ from 'moment'; |
20 | -import { Observable } from 'rxjs/internal/Observable'; | ||
21 | -import { from, of } from 'rxjs'; | ||
22 | -import { map, mergeMap, tap } from 'rxjs/operators'; | 20 | +import * as monentTz from 'moment-timezone'; |
23 | 21 | ||
24 | const moment = moment_; | 22 | const moment = moment_; |
25 | 23 | ||
@@ -27,6 +25,7 @@ export const SECOND = 1000; | @@ -27,6 +25,7 @@ export const SECOND = 1000; | ||
27 | export const MINUTE = 60 * SECOND; | 25 | export const MINUTE = 60 * SECOND; |
28 | export const HOUR = 60 * MINUTE; | 26 | export const HOUR = 60 * MINUTE; |
29 | export const DAY = 24 * HOUR; | 27 | export const DAY = 24 * HOUR; |
28 | +export const WEEK = 7 * DAY; | ||
30 | export const YEAR = DAY * 365; | 29 | export const YEAR = DAY * 365; |
31 | 30 | ||
32 | export enum TimewindowType { | 31 | export enum TimewindowType { |
@@ -34,14 +33,25 @@ export enum TimewindowType { | @@ -34,14 +33,25 @@ export enum TimewindowType { | ||
34 | HISTORY | 33 | HISTORY |
35 | } | 34 | } |
36 | 35 | ||
36 | +export enum RealtimeWindowType { | ||
37 | + LAST_INTERVAL, | ||
38 | + INTERVAL | ||
39 | +} | ||
40 | + | ||
37 | export enum HistoryWindowType { | 41 | export enum HistoryWindowType { |
38 | LAST_INTERVAL, | 42 | LAST_INTERVAL, |
39 | - FIXED | 43 | + FIXED, |
44 | + INTERVAL | ||
40 | } | 45 | } |
41 | 46 | ||
42 | export interface IntervalWindow { | 47 | export interface IntervalWindow { |
43 | interval?: number; | 48 | interval?: number; |
44 | timewindowMs?: number; | 49 | timewindowMs?: number; |
50 | + quickInterval?: QuickTimeInterval; | ||
51 | +} | ||
52 | + | ||
53 | +export interface RealtimeWindow extends IntervalWindow{ | ||
54 | + realtimeType?: RealtimeWindowType; | ||
45 | } | 55 | } |
46 | 56 | ||
47 | export interface FixedWindow { | 57 | export interface FixedWindow { |
@@ -82,13 +92,16 @@ export interface Aggregation { | @@ -82,13 +92,16 @@ export interface Aggregation { | ||
82 | 92 | ||
83 | export interface Timewindow { | 93 | export interface Timewindow { |
84 | displayValue?: string; | 94 | displayValue?: string; |
95 | + displayTimezoneAbbr?: string; | ||
85 | hideInterval?: boolean; | 96 | hideInterval?: boolean; |
86 | hideAggregation?: boolean; | 97 | hideAggregation?: boolean; |
87 | hideAggInterval?: boolean; | 98 | hideAggInterval?: boolean; |
99 | + hideTimezone?: boolean; | ||
88 | selectedTab?: TimewindowType; | 100 | selectedTab?: TimewindowType; |
89 | - realtime?: IntervalWindow; | 101 | + realtime?: RealtimeWindow; |
90 | history?: HistoryWindow; | 102 | history?: HistoryWindow; |
91 | aggregation?: Aggregation; | 103 | aggregation?: Aggregation; |
104 | + timezone?: string; | ||
92 | } | 105 | } |
93 | 106 | ||
94 | export interface SubscriptionAggregation extends Aggregation { | 107 | export interface SubscriptionAggregation extends Aggregation { |
@@ -99,6 +112,9 @@ export interface SubscriptionAggregation extends Aggregation { | @@ -99,6 +112,9 @@ export interface SubscriptionAggregation extends Aggregation { | ||
99 | 112 | ||
100 | export interface SubscriptionTimewindow { | 113 | export interface SubscriptionTimewindow { |
101 | startTs?: number; | 114 | startTs?: number; |
115 | + quickInterval?: QuickTimeInterval; | ||
116 | + timezone?: string; | ||
117 | + tsOffset?: number; | ||
102 | realtimeWindowMs?: number; | 118 | realtimeWindowMs?: number; |
103 | fixedWindow?: FixedWindow; | 119 | fixedWindow?: FixedWindow; |
104 | aggregation?: SubscriptionAggregation; | 120 | aggregation?: SubscriptionAggregation; |
@@ -108,9 +124,46 @@ export interface WidgetTimewindow { | @@ -108,9 +124,46 @@ export interface WidgetTimewindow { | ||
108 | minTime?: number; | 124 | minTime?: number; |
109 | maxTime?: number; | 125 | maxTime?: number; |
110 | interval?: number; | 126 | interval?: number; |
127 | + timezone?: string; | ||
111 | stDiff?: number; | 128 | stDiff?: number; |
112 | } | 129 | } |
113 | 130 | ||
131 | +export enum QuickTimeInterval { | ||
132 | + YESTERDAY = 'YESTERDAY', | ||
133 | + DAY_BEFORE_YESTERDAY = 'DAY_BEFORE_YESTERDAY', | ||
134 | + THIS_DAY_LAST_WEEK = 'THIS_DAY_LAST_WEEK', | ||
135 | + PREVIOUS_WEEK = 'PREVIOUS_WEEK', | ||
136 | + PREVIOUS_MONTH = 'PREVIOUS_MONTH', | ||
137 | + PREVIOUS_YEAR = 'PREVIOUS_YEAR', | ||
138 | + CURRENT_HOUR = 'CURRENT_HOUR', | ||
139 | + CURRENT_DAY = 'CURRENT_DAY', | ||
140 | + CURRENT_DAY_SO_FAR = 'CURRENT_DAY_SO_FAR', | ||
141 | + CURRENT_WEEK = 'CURRENT_WEEK', | ||
142 | + CURRENT_WEEK_SO_FAR = 'CURRENT_WEEK_SO_WAR', | ||
143 | + CURRENT_MONTH = 'CURRENT_MONTH', | ||
144 | + CURRENT_MONTH_SO_FAR = 'CURRENT_MONTH_SO_FAR', | ||
145 | + CURRENT_YEAR = 'CURRENT_YEAR', | ||
146 | + CURRENT_YEAR_SO_FAR = 'CURRENT_YEAR_SO_FAR' | ||
147 | +} | ||
148 | + | ||
149 | +export const QuickTimeIntervalTranslationMap = new Map<QuickTimeInterval, string>([ | ||
150 | + [QuickTimeInterval.YESTERDAY, 'timeinterval.predefined.yesterday'], | ||
151 | + [QuickTimeInterval.DAY_BEFORE_YESTERDAY, 'timeinterval.predefined.day-before-yesterday'], | ||
152 | + [QuickTimeInterval.THIS_DAY_LAST_WEEK, 'timeinterval.predefined.this-day-last-week'], | ||
153 | + [QuickTimeInterval.PREVIOUS_WEEK, 'timeinterval.predefined.previous-week'], | ||
154 | + [QuickTimeInterval.PREVIOUS_MONTH, 'timeinterval.predefined.previous-month'], | ||
155 | + [QuickTimeInterval.PREVIOUS_YEAR, 'timeinterval.predefined.previous-year'], | ||
156 | + [QuickTimeInterval.CURRENT_HOUR, 'timeinterval.predefined.current-hour'], | ||
157 | + [QuickTimeInterval.CURRENT_DAY, 'timeinterval.predefined.current-day'], | ||
158 | + [QuickTimeInterval.CURRENT_DAY_SO_FAR, 'timeinterval.predefined.current-day-so-far'], | ||
159 | + [QuickTimeInterval.CURRENT_WEEK, 'timeinterval.predefined.current-week'], | ||
160 | + [QuickTimeInterval.CURRENT_WEEK_SO_FAR, 'timeinterval.predefined.current-week-so-far'], | ||
161 | + [QuickTimeInterval.CURRENT_MONTH, 'timeinterval.predefined.current-month'], | ||
162 | + [QuickTimeInterval.CURRENT_MONTH_SO_FAR, 'timeinterval.predefined.current-month-so-far'], | ||
163 | + [QuickTimeInterval.CURRENT_YEAR, 'timeinterval.predefined.current-year'], | ||
164 | + [QuickTimeInterval.CURRENT_YEAR_SO_FAR, 'timeinterval.predefined.current-year-so-far'] | ||
165 | +]); | ||
166 | + | ||
114 | export function historyInterval(timewindowMs: number): Timewindow { | 167 | export function historyInterval(timewindowMs: number): Timewindow { |
115 | const timewindow: Timewindow = { | 168 | const timewindow: Timewindow = { |
116 | selectedTab: TimewindowType.HISTORY, | 169 | selectedTab: TimewindowType.HISTORY, |
@@ -129,10 +182,13 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { | @@ -129,10 +182,13 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { | ||
129 | hideInterval: false, | 182 | hideInterval: false, |
130 | hideAggregation: false, | 183 | hideAggregation: false, |
131 | hideAggInterval: false, | 184 | hideAggInterval: false, |
185 | + hideTimezone: false, | ||
132 | selectedTab: TimewindowType.REALTIME, | 186 | selectedTab: TimewindowType.REALTIME, |
133 | realtime: { | 187 | realtime: { |
188 | + realtimeType: RealtimeWindowType.LAST_INTERVAL, | ||
134 | interval: SECOND, | 189 | interval: SECOND, |
135 | - timewindowMs: MINUTE | 190 | + timewindowMs: MINUTE, |
191 | + quickInterval: QuickTimeInterval.CURRENT_DAY | ||
136 | }, | 192 | }, |
137 | history: { | 193 | history: { |
138 | historyType: HistoryWindowType.LAST_INTERVAL, | 194 | historyType: HistoryWindowType.LAST_INTERVAL, |
@@ -141,7 +197,8 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { | @@ -141,7 +197,8 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { | ||
141 | fixedTimewindow: { | 197 | fixedTimewindow: { |
142 | startTimeMs: currentTime - DAY, | 198 | startTimeMs: currentTime - DAY, |
143 | endTimeMs: currentTime | 199 | endTimeMs: currentTime |
144 | - } | 200 | + }, |
201 | + quickInterval: QuickTimeInterval.CURRENT_DAY | ||
145 | }, | 202 | }, |
146 | aggregation: { | 203 | aggregation: { |
147 | type: AggregationType.AVG, | 204 | type: AggregationType.AVG, |
@@ -157,6 +214,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | @@ -157,6 +214,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | ||
157 | model.hideInterval = value.hideInterval; | 214 | model.hideInterval = value.hideInterval; |
158 | model.hideAggregation = value.hideAggregation; | 215 | model.hideAggregation = value.hideAggregation; |
159 | model.hideAggInterval = value.hideAggInterval; | 216 | model.hideAggInterval = value.hideAggInterval; |
217 | + model.hideTimezone = value.hideTimezone; | ||
160 | if (isUndefined(value.selectedTab)) { | 218 | if (isUndefined(value.selectedTab)) { |
161 | if (value.realtime) { | 219 | if (value.realtime) { |
162 | model.selectedTab = TimewindowType.REALTIME; | 220 | model.selectedTab = TimewindowType.REALTIME; |
@@ -170,7 +228,20 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | @@ -170,7 +228,20 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | ||
170 | if (isDefined(value.realtime.interval)) { | 228 | if (isDefined(value.realtime.interval)) { |
171 | model.realtime.interval = value.realtime.interval; | 229 | model.realtime.interval = value.realtime.interval; |
172 | } | 230 | } |
173 | - model.realtime.timewindowMs = value.realtime.timewindowMs; | 231 | + if (isUndefined(value.realtime.realtimeType)) { |
232 | + if (isDefined(value.realtime.quickInterval)) { | ||
233 | + model.realtime.realtimeType = RealtimeWindowType.INTERVAL; | ||
234 | + } else { | ||
235 | + model.realtime.realtimeType = RealtimeWindowType.LAST_INTERVAL; | ||
236 | + } | ||
237 | + } else { | ||
238 | + model.realtime.realtimeType = value.realtime.realtimeType; | ||
239 | + } | ||
240 | + if (model.realtime.realtimeType === RealtimeWindowType.INTERVAL) { | ||
241 | + model.realtime.quickInterval = value.realtime.quickInterval; | ||
242 | + } else { | ||
243 | + model.realtime.timewindowMs = value.realtime.timewindowMs; | ||
244 | + } | ||
174 | } else { | 245 | } else { |
175 | if (isDefined(value.history.interval)) { | 246 | if (isDefined(value.history.interval)) { |
176 | model.history.interval = value.history.interval; | 247 | model.history.interval = value.history.interval; |
@@ -178,6 +249,8 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | @@ -178,6 +249,8 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | ||
178 | if (isUndefined(value.history.historyType)) { | 249 | if (isUndefined(value.history.historyType)) { |
179 | if (isDefined(value.history.timewindowMs)) { | 250 | if (isDefined(value.history.timewindowMs)) { |
180 | model.history.historyType = HistoryWindowType.LAST_INTERVAL; | 251 | model.history.historyType = HistoryWindowType.LAST_INTERVAL; |
252 | + } else if (isDefined(value.history.quickInterval)) { | ||
253 | + model.history.historyType = HistoryWindowType.INTERVAL; | ||
181 | } else { | 254 | } else { |
182 | model.history.historyType = HistoryWindowType.FIXED; | 255 | model.history.historyType = HistoryWindowType.FIXED; |
183 | } | 256 | } |
@@ -186,6 +259,8 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | @@ -186,6 +259,8 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | ||
186 | } | 259 | } |
187 | if (model.history.historyType === HistoryWindowType.LAST_INTERVAL) { | 260 | if (model.history.historyType === HistoryWindowType.LAST_INTERVAL) { |
188 | model.history.timewindowMs = value.history.timewindowMs; | 261 | model.history.timewindowMs = value.history.timewindowMs; |
262 | + } else if (model.history.historyType === HistoryWindowType.INTERVAL) { | ||
263 | + model.history.quickInterval = value.history.quickInterval; | ||
189 | } else { | 264 | } else { |
190 | model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs; | 265 | model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs; |
191 | model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs; | 266 | model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs; |
@@ -197,6 +272,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | @@ -197,6 +272,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T | ||
197 | } | 272 | } |
198 | model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); | 273 | model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); |
199 | } | 274 | } |
275 | + model.timezone = value.timezone; | ||
200 | } | 276 | } |
201 | return model; | 277 | return model; |
202 | } | 278 | } |
@@ -223,6 +299,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | @@ -223,6 +299,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | ||
223 | hideInterval: timewindow.hideInterval || false, | 299 | hideInterval: timewindow.hideInterval || false, |
224 | hideAggregation: timewindow.hideAggregation || false, | 300 | hideAggregation: timewindow.hideAggregation || false, |
225 | hideAggInterval: timewindow.hideAggInterval || false, | 301 | hideAggInterval: timewindow.hideAggInterval || false, |
302 | + hideTimezone: timewindow.hideTimezone || false, | ||
226 | selectedTab: TimewindowType.HISTORY, | 303 | selectedTab: TimewindowType.HISTORY, |
227 | history: { | 304 | history: { |
228 | historyType: HistoryWindowType.FIXED, | 305 | historyType: HistoryWindowType.FIXED, |
@@ -235,11 +312,22 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | @@ -235,11 +312,22 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, | ||
235 | aggregation: { | 312 | aggregation: { |
236 | type: aggType, | 313 | type: aggType, |
237 | limit | 314 | limit |
238 | - } | 315 | + }, |
316 | + timezone: timewindow.timezone | ||
239 | }; | 317 | }; |
240 | return historyTimewindow; | 318 | return historyTimewindow; |
241 | } | 319 | } |
242 | 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 | + | ||
243 | export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean, | 331 | export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean, |
244 | timeService: TimeService): SubscriptionTimewindow { | 332 | timeService: TimeService): SubscriptionTimewindow { |
245 | const subscriptionTimewindow: SubscriptionTimewindow = { | 333 | const subscriptionTimewindow: SubscriptionTimewindow = { |
@@ -249,7 +337,9 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | @@ -249,7 +337,9 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | ||
249 | interval: SECOND, | 337 | interval: SECOND, |
250 | limit: timeService.getMaxDatapointsLimit(), | 338 | limit: timeService.getMaxDatapointsLimit(), |
251 | type: AggregationType.AVG | 339 | type: AggregationType.AVG |
252 | - } | 340 | + }, |
341 | + timezone: timewindow.timezone, | ||
342 | + tsOffset: calculateTsOffset(timewindow.timezone) | ||
253 | }; | 343 | }; |
254 | let aggTimewindow = 0; | 344 | let aggTimewindow = 0; |
255 | if (stateData) { | 345 | if (stateData) { |
@@ -267,33 +357,66 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | @@ -267,33 +357,66 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | ||
267 | selectedTab = isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY; | 357 | selectedTab = isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY; |
268 | } | 358 | } |
269 | if (selectedTab === TimewindowType.REALTIME) { | 359 | if (selectedTab === TimewindowType.REALTIME) { |
270 | - subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; | 360 | + let realtimeType = timewindow.realtime.realtimeType; |
361 | + if (isUndefined(realtimeType)) { | ||
362 | + if (isDefined(timewindow.realtime.quickInterval)) { | ||
363 | + realtimeType = RealtimeWindowType.INTERVAL; | ||
364 | + } else { | ||
365 | + realtimeType = RealtimeWindowType.LAST_INTERVAL; | ||
366 | + } | ||
367 | + } | ||
368 | + if (realtimeType === RealtimeWindowType.INTERVAL) { | ||
369 | + const currentDate = getCurrentTime(timewindow.timezone); | ||
370 | + subscriptionTimewindow.realtimeWindowMs = | ||
371 | + getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, currentDate); | ||
372 | + subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval; | ||
373 | + subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate); | ||
374 | + } else { | ||
375 | + subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; | ||
376 | + subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs; | ||
377 | + } | ||
271 | subscriptionTimewindow.aggregation.interval = | 378 | subscriptionTimewindow.aggregation.interval = |
272 | timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, | 379 | timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, |
273 | subscriptionTimewindow.aggregation.type); | 380 | subscriptionTimewindow.aggregation.type); |
274 | - subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs; | ||
275 | - const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval; | ||
276 | aggTimewindow = subscriptionTimewindow.realtimeWindowMs; | 381 | aggTimewindow = subscriptionTimewindow.realtimeWindowMs; |
277 | - if (startDiff) { | ||
278 | - subscriptionTimewindow.startTs -= startDiff; | ||
279 | - aggTimewindow += subscriptionTimewindow.aggregation.interval; | 382 | + if (realtimeType !== RealtimeWindowType.INTERVAL) { |
383 | + const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval; | ||
384 | + if (startDiff) { | ||
385 | + subscriptionTimewindow.startTs -= startDiff; | ||
386 | + aggTimewindow += subscriptionTimewindow.aggregation.interval; | ||
387 | + } | ||
280 | } | 388 | } |
281 | } else { | 389 | } else { |
282 | let historyType = timewindow.history.historyType; | 390 | let historyType = timewindow.history.historyType; |
283 | if (isUndefined(historyType)) { | 391 | if (isUndefined(historyType)) { |
284 | - historyType = isDefined(timewindow.history.timewindowMs) ? HistoryWindowType.LAST_INTERVAL : HistoryWindowType.FIXED; | 392 | + if (isDefined(timewindow.history.timewindowMs)) { |
393 | + historyType = HistoryWindowType.LAST_INTERVAL; | ||
394 | + } else if (isDefined(timewindow.history.quickInterval)) { | ||
395 | + historyType = HistoryWindowType.INTERVAL; | ||
396 | + } else { | ||
397 | + historyType = HistoryWindowType.FIXED; | ||
398 | + } | ||
285 | } | 399 | } |
286 | if (historyType === HistoryWindowType.LAST_INTERVAL) { | 400 | if (historyType === HistoryWindowType.LAST_INTERVAL) { |
287 | - const currentTime = Date.now(); | 401 | + const currentDate = getCurrentTime(timewindow.timezone); |
402 | + const currentTime = currentDate.valueOf(); | ||
288 | subscriptionTimewindow.fixedWindow = { | 403 | subscriptionTimewindow.fixedWindow = { |
289 | startTimeMs: currentTime - timewindow.history.timewindowMs, | 404 | startTimeMs: currentTime - timewindow.history.timewindowMs, |
290 | endTimeMs: currentTime | 405 | endTimeMs: currentTime |
291 | }; | 406 | }; |
292 | aggTimewindow = timewindow.history.timewindowMs; | 407 | aggTimewindow = timewindow.history.timewindowMs; |
408 | + } else if (historyType === HistoryWindowType.INTERVAL) { | ||
409 | + const currentDate = getCurrentTime(timewindow.timezone); | ||
410 | + subscriptionTimewindow.fixedWindow = { | ||
411 | + startTimeMs: calculateIntervalStartTime(timewindow.history.quickInterval, currentDate), | ||
412 | + endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate) | ||
413 | + }; | ||
414 | + aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; | ||
415 | + subscriptionTimewindow.quickInterval = timewindow.history.quickInterval; | ||
293 | } else { | 416 | } else { |
294 | subscriptionTimewindow.fixedWindow = { | 417 | subscriptionTimewindow.fixedWindow = { |
295 | - startTimeMs: timewindow.history.fixedTimewindow.startTimeMs, | ||
296 | - endTimeMs: timewindow.history.fixedTimewindow.endTimeMs | 418 | + startTimeMs: timewindow.history.fixedTimewindow.startTimeMs - subscriptionTimewindow.tsOffset, |
419 | + endTimeMs: timewindow.history.fixedTimewindow.endTimeMs - subscriptionTimewindow.tsOffset | ||
297 | }; | 420 | }; |
298 | aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; | 421 | aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; |
299 | } | 422 | } |
@@ -309,6 +432,127 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | @@ -309,6 +432,127 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num | ||
309 | return subscriptionTimewindow; | 432 | return subscriptionTimewindow; |
310 | } | 433 | } |
311 | 434 | ||
435 | +function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval, currentDate: moment_.Moment): number { | ||
436 | + switch (interval) { | ||
437 | + case QuickTimeInterval.CURRENT_HOUR: | ||
438 | + return HOUR; | ||
439 | + case QuickTimeInterval.CURRENT_DAY: | ||
440 | + case QuickTimeInterval.CURRENT_DAY_SO_FAR: | ||
441 | + return DAY; | ||
442 | + case QuickTimeInterval.CURRENT_WEEK: | ||
443 | + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: | ||
444 | + return WEEK; | ||
445 | + case QuickTimeInterval.CURRENT_MONTH: | ||
446 | + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: | ||
447 | + return currentDate.endOf('month').diff(currentDate.clone().startOf('month')); | ||
448 | + case QuickTimeInterval.CURRENT_YEAR: | ||
449 | + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: | ||
450 | + return currentDate.endOf('year').diff(currentDate.clone().startOf('year')); | ||
451 | + } | ||
452 | +} | ||
453 | + | ||
454 | +export function calculateIntervalEndTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number { | ||
455 | + currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz); | ||
456 | + switch (interval) { | ||
457 | + case QuickTimeInterval.YESTERDAY: | ||
458 | + currentDate.subtract(1, 'days'); | ||
459 | + return currentDate.endOf('day').valueOf(); | ||
460 | + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: | ||
461 | + currentDate.subtract(2, 'days'); | ||
462 | + return currentDate.endOf('day').valueOf(); | ||
463 | + case QuickTimeInterval.THIS_DAY_LAST_WEEK: | ||
464 | + currentDate.subtract(1, 'weeks'); | ||
465 | + return currentDate.endOf('day').valueOf(); | ||
466 | + case QuickTimeInterval.PREVIOUS_WEEK: | ||
467 | + currentDate.subtract(1, 'weeks'); | ||
468 | + return currentDate.endOf('week').valueOf(); | ||
469 | + case QuickTimeInterval.PREVIOUS_MONTH: | ||
470 | + currentDate.subtract(1, 'months'); | ||
471 | + return currentDate.endOf('month').valueOf(); | ||
472 | + case QuickTimeInterval.PREVIOUS_YEAR: | ||
473 | + currentDate.subtract(1, 'years'); | ||
474 | + return currentDate.endOf('year').valueOf(); | ||
475 | + case QuickTimeInterval.CURRENT_HOUR: | ||
476 | + return currentDate.endOf('hour').valueOf(); | ||
477 | + case QuickTimeInterval.CURRENT_DAY: | ||
478 | + return currentDate.endOf('day').valueOf(); | ||
479 | + case QuickTimeInterval.CURRENT_WEEK: | ||
480 | + return currentDate.endOf('week').valueOf(); | ||
481 | + case QuickTimeInterval.CURRENT_MONTH: | ||
482 | + return currentDate.endOf('month').valueOf(); | ||
483 | + case QuickTimeInterval.CURRENT_YEAR: | ||
484 | + return currentDate.endOf('year').valueOf(); | ||
485 | + case QuickTimeInterval.CURRENT_DAY_SO_FAR: | ||
486 | + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: | ||
487 | + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: | ||
488 | + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: | ||
489 | + return currentDate.valueOf(); | ||
490 | + } | ||
491 | +} | ||
492 | + | ||
493 | +export function calculateIntervalStartTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number { | ||
494 | + currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz); | ||
495 | + switch (interval) { | ||
496 | + case QuickTimeInterval.YESTERDAY: | ||
497 | + currentDate.subtract(1, 'days'); | ||
498 | + return currentDate.startOf('day').valueOf(); | ||
499 | + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: | ||
500 | + currentDate.subtract(2, 'days'); | ||
501 | + return currentDate.startOf('day').valueOf(); | ||
502 | + case QuickTimeInterval.THIS_DAY_LAST_WEEK: | ||
503 | + currentDate.subtract(1, 'weeks'); | ||
504 | + return currentDate.startOf('day').valueOf(); | ||
505 | + case QuickTimeInterval.PREVIOUS_WEEK: | ||
506 | + currentDate.subtract(1, 'weeks'); | ||
507 | + return currentDate.startOf('week').valueOf(); | ||
508 | + case QuickTimeInterval.PREVIOUS_MONTH: | ||
509 | + currentDate.subtract(1, 'months'); | ||
510 | + return currentDate.startOf('month').valueOf(); | ||
511 | + case QuickTimeInterval.PREVIOUS_YEAR: | ||
512 | + currentDate.subtract(1, 'years'); | ||
513 | + return currentDate.startOf('year').valueOf(); | ||
514 | + case QuickTimeInterval.CURRENT_HOUR: | ||
515 | + return currentDate.startOf('hour').valueOf(); | ||
516 | + case QuickTimeInterval.CURRENT_DAY: | ||
517 | + case QuickTimeInterval.CURRENT_DAY_SO_FAR: | ||
518 | + return currentDate.startOf('day').valueOf(); | ||
519 | + case QuickTimeInterval.CURRENT_WEEK: | ||
520 | + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: | ||
521 | + return currentDate.startOf('week').valueOf(); | ||
522 | + case QuickTimeInterval.CURRENT_MONTH: | ||
523 | + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: | ||
524 | + return currentDate.startOf('month').valueOf(); | ||
525 | + case QuickTimeInterval.CURRENT_YEAR: | ||
526 | + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: | ||
527 | + return currentDate.startOf('year').valueOf(); | ||
528 | + } | ||
529 | +} | ||
530 | + | ||
531 | +export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number { | ||
532 | + switch (interval) { | ||
533 | + case QuickTimeInterval.CURRENT_HOUR: | ||
534 | + return HOUR; | ||
535 | + case QuickTimeInterval.YESTERDAY: | ||
536 | + case QuickTimeInterval.DAY_BEFORE_YESTERDAY: | ||
537 | + case QuickTimeInterval.THIS_DAY_LAST_WEEK: | ||
538 | + case QuickTimeInterval.CURRENT_DAY: | ||
539 | + case QuickTimeInterval.CURRENT_DAY_SO_FAR: | ||
540 | + return DAY; | ||
541 | + case QuickTimeInterval.PREVIOUS_WEEK: | ||
542 | + case QuickTimeInterval.CURRENT_WEEK: | ||
543 | + case QuickTimeInterval.CURRENT_WEEK_SO_FAR: | ||
544 | + return WEEK; | ||
545 | + case QuickTimeInterval.PREVIOUS_MONTH: | ||
546 | + case QuickTimeInterval.CURRENT_MONTH: | ||
547 | + case QuickTimeInterval.CURRENT_MONTH_SO_FAR: | ||
548 | + return DAY * 30; | ||
549 | + case QuickTimeInterval.PREVIOUS_YEAR: | ||
550 | + case QuickTimeInterval.CURRENT_YEAR: | ||
551 | + case QuickTimeInterval.CURRENT_YEAR_SO_FAR: | ||
552 | + return YEAR; | ||
553 | + } | ||
554 | +} | ||
555 | + | ||
312 | export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow, | 556 | export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow, |
313 | timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow { | 557 | timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow { |
314 | const timewindowForComparison: SubscriptionTimewindow = { | 558 | const timewindowForComparison: SubscriptionTimewindow = { |
@@ -339,6 +583,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { | @@ -339,6 +583,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { | ||
339 | cloned.hideInterval = timewindow.hideInterval || false; | 583 | cloned.hideInterval = timewindow.hideInterval || false; |
340 | cloned.hideAggregation = timewindow.hideAggregation || false; | 584 | cloned.hideAggregation = timewindow.hideAggregation || false; |
341 | cloned.hideAggInterval = timewindow.hideAggInterval || false; | 585 | cloned.hideAggInterval = timewindow.hideAggInterval || false; |
586 | + cloned.hideTimezone = timewindow.hideTimezone || false; | ||
342 | if (isDefined(timewindow.selectedTab)) { | 587 | if (isDefined(timewindow.selectedTab)) { |
343 | cloned.selectedTab = timewindow.selectedTab; | 588 | cloned.selectedTab = timewindow.selectedTab; |
344 | if (timewindow.selectedTab === TimewindowType.REALTIME) { | 589 | if (timewindow.selectedTab === TimewindowType.REALTIME) { |
@@ -348,6 +593,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { | @@ -348,6 +593,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { | ||
348 | } | 593 | } |
349 | } | 594 | } |
350 | cloned.aggregation = deepClone(timewindow.aggregation); | 595 | cloned.aggregation = deepClone(timewindow.aggregation); |
596 | + cloned.timezone = timewindow.timezone; | ||
351 | return cloned; | 597 | return cloned; |
352 | } | 598 | } |
353 | 599 | ||
@@ -358,6 +604,8 @@ export function cloneSelectedHistoryTimewindow(historyWindow: HistoryWindow): Hi | @@ -358,6 +604,8 @@ export function cloneSelectedHistoryTimewindow(historyWindow: HistoryWindow): Hi | ||
358 | cloned.interval = historyWindow.interval; | 604 | cloned.interval = historyWindow.interval; |
359 | if (historyWindow.historyType === HistoryWindowType.LAST_INTERVAL) { | 605 | if (historyWindow.historyType === HistoryWindowType.LAST_INTERVAL) { |
360 | cloned.timewindowMs = historyWindow.timewindowMs; | 606 | cloned.timewindowMs = historyWindow.timewindowMs; |
607 | + } else if (historyWindow.historyType === HistoryWindowType.INTERVAL) { | ||
608 | + cloned.quickInterval = historyWindow.quickInterval; | ||
361 | } else if (historyWindow.historyType === HistoryWindowType.FIXED) { | 609 | } else if (historyWindow.historyType === HistoryWindowType.FIXED) { |
362 | cloned.fixedTimewindow = deepClone(historyWindow.fixedTimewindow); | 610 | cloned.fixedTimewindow = deepClone(historyWindow.fixedTimewindow); |
363 | } | 611 | } |
@@ -375,7 +623,7 @@ export const defaultTimeIntervals = new Array<TimeInterval>( | @@ -375,7 +623,7 @@ export const defaultTimeIntervals = new Array<TimeInterval>( | ||
375 | { | 623 | { |
376 | name: 'timeinterval.seconds-interval', | 624 | name: 'timeinterval.seconds-interval', |
377 | translateParams: {seconds: 1}, | 625 | translateParams: {seconds: 1}, |
378 | - value: 1 * SECOND | 626 | + value: SECOND |
379 | }, | 627 | }, |
380 | { | 628 | { |
381 | name: 'timeinterval.seconds-interval', | 629 | name: 'timeinterval.seconds-interval', |
@@ -400,7 +648,7 @@ export const defaultTimeIntervals = new Array<TimeInterval>( | @@ -400,7 +648,7 @@ export const defaultTimeIntervals = new Array<TimeInterval>( | ||
400 | { | 648 | { |
401 | name: 'timeinterval.minutes-interval', | 649 | name: 'timeinterval.minutes-interval', |
402 | translateParams: {minutes: 1}, | 650 | translateParams: {minutes: 1}, |
403 | - value: 1 * MINUTE | 651 | + value: MINUTE |
404 | }, | 652 | }, |
405 | { | 653 | { |
406 | name: 'timeinterval.minutes-interval', | 654 | name: 'timeinterval.minutes-interval', |
@@ -430,7 +678,7 @@ export const defaultTimeIntervals = new Array<TimeInterval>( | @@ -430,7 +678,7 @@ export const defaultTimeIntervals = new Array<TimeInterval>( | ||
430 | { | 678 | { |
431 | name: 'timeinterval.hours-interval', | 679 | name: 'timeinterval.hours-interval', |
432 | translateParams: {hours: 1}, | 680 | translateParams: {hours: 1}, |
433 | - value: 1 * HOUR | 681 | + value: HOUR |
434 | }, | 682 | }, |
435 | { | 683 | { |
436 | name: 'timeinterval.hours-interval', | 684 | name: 'timeinterval.hours-interval', |
@@ -455,7 +703,7 @@ export const defaultTimeIntervals = new Array<TimeInterval>( | @@ -455,7 +703,7 @@ export const defaultTimeIntervals = new Array<TimeInterval>( | ||
455 | { | 703 | { |
456 | name: 'timeinterval.days-interval', | 704 | name: 'timeinterval.days-interval', |
457 | translateParams: {days: 1}, | 705 | translateParams: {days: 1}, |
458 | - value: 1 * DAY | 706 | + value: DAY |
459 | }, | 707 | }, |
460 | { | 708 | { |
461 | name: 'timeinterval.days-interval', | 709 | name: 'timeinterval.days-interval', |
@@ -490,65 +738,62 @@ export interface TimezoneInfo { | @@ -490,65 +738,62 @@ export interface TimezoneInfo { | ||
490 | name: string; | 738 | name: string; |
491 | offset: string; | 739 | offset: string; |
492 | nOffset: number; | 740 | nOffset: number; |
741 | + abbr: string; | ||
493 | } | 742 | } |
494 | 743 | ||
495 | let timezones: TimezoneInfo[] = null; | 744 | let timezones: TimezoneInfo[] = null; |
496 | let defaultTimezone: string = null; | 745 | let defaultTimezone: string = null; |
497 | 746 | ||
498 | -export function getTimezones(): Observable<TimezoneInfo[]> { | ||
499 | - if (timezones) { | ||
500 | - return of(timezones); | ||
501 | - } else { | ||
502 | - return from(import('moment-timezone')).pipe( | ||
503 | - map((monentTz) => { | ||
504 | - return monentTz.tz.names().map((zoneName) => { | ||
505 | - const tz = monentTz.tz(zoneName); | ||
506 | - return { | ||
507 | - id: zoneName, | ||
508 | - name: zoneName.replace(/_/g, ' '), | ||
509 | - offset: `UTC${tz.format('Z')}`, | ||
510 | - nOffset: tz.utcOffset() | ||
511 | - }; | ||
512 | - }); | ||
513 | - }), | ||
514 | - tap((zones) => { | ||
515 | - timezones = zones; | ||
516 | - }) | ||
517 | - ); | 747 | +export function getTimezones(): TimezoneInfo[] { |
748 | + if (!timezones) { | ||
749 | + timezones = monentTz.tz.names().map((zoneName) => { | ||
750 | + const tz = monentTz.tz(zoneName); | ||
751 | + return { | ||
752 | + id: zoneName, | ||
753 | + name: zoneName.replace(/_/g, ' '), | ||
754 | + offset: `UTC${tz.format('Z')}`, | ||
755 | + nOffset: tz.utcOffset(), | ||
756 | + abbr: tz.zoneAbbr() | ||
757 | + }; | ||
758 | + }); | ||
518 | } | 759 | } |
760 | + return timezones; | ||
519 | } | 761 | } |
520 | 762 | ||
521 | -export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): Observable<TimezoneInfo> { | ||
522 | - return getTimezones().pipe( | ||
523 | - mergeMap((timezoneList) => { | ||
524 | - let foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId); | ||
525 | - if (!foundTimezone) { | ||
526 | - if (userTimezoneByDefault) { | ||
527 | - return getDefaultTimezone().pipe( | ||
528 | - map((userTimezone) => { | ||
529 | - return timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone); | ||
530 | - }) | ||
531 | - ); | ||
532 | - } else if (defaultTimezoneId) { | ||
533 | - foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId); | ||
534 | - } | ||
535 | - } | ||
536 | - return of(foundTimezone); | ||
537 | - }) | ||
538 | - ); | 763 | +export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): TimezoneInfo { |
764 | + const timezoneList = getTimezones(); | ||
765 | + let foundTimezone = timezoneId ? timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId) : null; | ||
766 | + if (!foundTimezone) { | ||
767 | + if (userTimezoneByDefault) { | ||
768 | + const userTimezone = getDefaultTimezone(); | ||
769 | + foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone); | ||
770 | + } else if (defaultTimezoneId) { | ||
771 | + foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId); | ||
772 | + } | ||
773 | + } | ||
774 | + return foundTimezone; | ||
539 | } | 775 | } |
540 | 776 | ||
541 | -export function getDefaultTimezone(): Observable<string> { | ||
542 | - if (defaultTimezone) { | ||
543 | - return of(defaultTimezone); | 777 | +export function getDefaultTimezoneInfo(): TimezoneInfo { |
778 | + const userTimezone = getDefaultTimezone(); | ||
779 | + return getTimezoneInfo(userTimezone); | ||
780 | +} | ||
781 | + | ||
782 | +export function getDefaultTimezone(): string { | ||
783 | + if (!defaultTimezone) { | ||
784 | + defaultTimezone = monentTz.tz.guess(); | ||
785 | + } | ||
786 | + return defaultTimezone; | ||
787 | +} | ||
788 | + | ||
789 | +export function getCurrentTime(tz?: string): moment_.Moment { | ||
790 | + if (tz) { | ||
791 | + return moment().tz(tz); | ||
544 | } else { | 792 | } else { |
545 | - return from(import('moment-timezone')).pipe( | ||
546 | - map((monentTz) => { | ||
547 | - return monentTz.tz.guess(); | ||
548 | - }), | ||
549 | - tap((zone) => { | ||
550 | - defaultTimezone = zone; | ||
551 | - }) | ||
552 | - ); | 793 | + return moment(); |
553 | } | 794 | } |
554 | } | 795 | } |
796 | + | ||
797 | +export function getTimezone(tz: string): moment_.Moment { | ||
798 | + return moment.tz(tz); | ||
799 | +} |
@@ -140,6 +140,7 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select | @@ -140,6 +140,7 @@ import { TimezoneSelectComponent } from '@shared/components/time/timezone-select | ||
140 | import { FileSizePipe } from '@shared/pipe/file-size.pipe'; | 140 | import { FileSizePipe } from '@shared/pipe/file-size.pipe'; |
141 | import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-search.component'; | 141 | import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-search.component'; |
142 | import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; | 142 | import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; |
143 | +import { QuickTimeIntervalComponent } from '@shared/components/time/quick-time-interval.component'; | ||
143 | 144 | ||
144 | @NgModule({ | 145 | @NgModule({ |
145 | providers: [ | 146 | providers: [ |
@@ -175,6 +176,7 @@ import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; | @@ -175,6 +176,7 @@ import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; | ||
175 | TimewindowComponent, | 176 | TimewindowComponent, |
176 | TimewindowPanelComponent, | 177 | TimewindowPanelComponent, |
177 | TimeintervalComponent, | 178 | TimeintervalComponent, |
179 | + QuickTimeIntervalComponent, | ||
178 | DashboardSelectComponent, | 180 | DashboardSelectComponent, |
179 | DashboardSelectPanelComponent, | 181 | DashboardSelectPanelComponent, |
180 | DatetimePeriodComponent, | 182 | DatetimePeriodComponent, |
@@ -302,6 +304,7 @@ import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; | @@ -302,6 +304,7 @@ import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; | ||
302 | TimewindowComponent, | 304 | TimewindowComponent, |
303 | TimewindowPanelComponent, | 305 | TimewindowPanelComponent, |
304 | TimeintervalComponent, | 306 | TimeintervalComponent, |
307 | + QuickTimeIntervalComponent, | ||
305 | DashboardSelectComponent, | 308 | DashboardSelectComponent, |
306 | DatetimePeriodComponent, | 309 | DatetimePeriodComponent, |
307 | DatetimeComponent, | 310 | DatetimeComponent, |
@@ -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", |
@@ -2118,7 +2119,24 @@ | @@ -2118,7 +2119,24 @@ | ||
2118 | "hours": "Hours", | 2119 | "hours": "Hours", |
2119 | "minutes": "Minutes", | 2120 | "minutes": "Minutes", |
2120 | "seconds": "Seconds", | 2121 | "seconds": "Seconds", |
2121 | - "advanced": "Advanced" | 2122 | + "advanced": "Advanced", |
2123 | + "predefined": { | ||
2124 | + "yesterday": "Yesterday", | ||
2125 | + "day-before-yesterday": "Day before yesterday", | ||
2126 | + "this-day-last-week": "This day last week", | ||
2127 | + "previous-week": "Previous week", | ||
2128 | + "previous-month": "Previous month", | ||
2129 | + "previous-year": "Previous year", | ||
2130 | + "current-hour": "Current hour", | ||
2131 | + "current-day": "Current day", | ||
2132 | + "current-day-so-far": "Current day so far", | ||
2133 | + "current-week": "Current week", | ||
2134 | + "current-week-so-far": "Current week so far", | ||
2135 | + "current-month": "Current month", | ||
2136 | + "current-month-so-far": "Current month so far", | ||
2137 | + "current-year": "Current year", | ||
2138 | + "current-year-so-far": "Current year so far" | ||
2139 | + } | ||
2122 | }, | 2140 | }, |
2123 | "timeunit": { | 2141 | "timeunit": { |
2124 | "seconds": "Seconds", | 2142 | "seconds": "Seconds", |
@@ -2139,7 +2157,8 @@ | @@ -2139,7 +2157,8 @@ | ||
2139 | "date-range": "Date range", | 2157 | "date-range": "Date range", |
2140 | "last": "Last", | 2158 | "last": "Last", |
2141 | "time-period": "Time period", | 2159 | "time-period": "Time period", |
2142 | - "hide": "Hide" | 2160 | + "hide": "Hide", |
2161 | + "interval": "Interval" | ||
2143 | }, | 2162 | }, |
2144 | "user": { | 2163 | "user": { |
2145 | "user": "User", | 2164 | "user": "User", |