Commit a14c10f1bcde77e9e161930eb87daed77b00bec6
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
16 changed files
with
220 additions
and
34 deletions
@@ -17,17 +17,23 @@ package org.thingsboard.server.common.data.query; | @@ -17,17 +17,23 @@ package org.thingsboard.server.common.data.query; | ||
17 | 17 | ||
18 | import com.fasterxml.jackson.annotation.JsonIgnore; | 18 | import com.fasterxml.jackson.annotation.JsonIgnore; |
19 | import lombok.Data; | 19 | import lombok.Data; |
20 | -import lombok.Getter; | 20 | +import lombok.RequiredArgsConstructor; |
21 | 21 | ||
22 | @Data | 22 | @Data |
23 | +@RequiredArgsConstructor | ||
23 | public class DynamicValue<T> { | 24 | public class DynamicValue<T> { |
24 | 25 | ||
25 | @JsonIgnore | 26 | @JsonIgnore |
26 | private T resolvedValue; | 27 | private T resolvedValue; |
27 | 28 | ||
28 | - @Getter | ||
29 | private final DynamicValueSourceType sourceType; | 29 | private final DynamicValueSourceType sourceType; |
30 | - @Getter | ||
31 | private final String sourceAttribute; | 30 | private final String sourceAttribute; |
31 | + private final boolean inherit; | ||
32 | + | ||
33 | + public DynamicValue(DynamicValueSourceType sourceType, String sourceAttribute) { | ||
34 | + this.sourceAttribute = sourceAttribute; | ||
35 | + this.sourceType = sourceType; | ||
36 | + this.inherit = false; | ||
37 | + } | ||
32 | 38 | ||
33 | } | 39 | } |
@@ -763,8 +763,8 @@ public class DefaultTransportService implements TransportService { | @@ -763,8 +763,8 @@ public class DefaultTransportService implements TransportService { | ||
763 | wrappedCallback); | 763 | wrappedCallback); |
764 | } | 764 | } |
765 | 765 | ||
766 | - protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { | ||
767 | - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator()); | 766 | + private void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { |
767 | + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, tbMsg.getOriginator()); | ||
768 | if (log.isTraceEnabled()) { | 768 | if (log.isTraceEnabled()) { |
769 | log.trace("[{}][{}] Pushing to topic {} message {}", tenantId, tbMsg.getOriginator(), tpi.getFullTopicName(), tbMsg); | 769 | log.trace("[{}][{}] Pushing to topic {} message {}", tenantId, tbMsg.getOriginator(), tpi.getFullTopicName(), tbMsg); |
770 | } | 770 | } |
@@ -776,7 +776,7 @@ public class DefaultTransportService implements TransportService { | @@ -776,7 +776,7 @@ public class DefaultTransportService implements TransportService { | ||
776 | ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), wrappedCallback); | 776 | ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), wrappedCallback); |
777 | } | 777 | } |
778 | 778 | ||
779 | - protected void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, | 779 | + private void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, |
780 | TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { | 780 | TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { |
781 | DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); | 781 | DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); |
782 | DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); | 782 | DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); |
@@ -104,9 +104,10 @@ public class TbMsgGeneratorNode implements TbNode { | @@ -104,9 +104,10 @@ public class TbMsgGeneratorNode implements TbNode { | ||
104 | } | 104 | } |
105 | }, | 105 | }, |
106 | t -> { | 106 | t -> { |
107 | - if (initialized) { | 107 | + if (initialized && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { |
108 | ctx.tellFailure(msg, t); | 108 | ctx.tellFailure(msg, t); |
109 | scheduleTickMsg(ctx); | 109 | scheduleTickMsg(ctx); |
110 | + currentMsgCount++; | ||
110 | } | 111 | } |
111 | }); | 112 | }); |
112 | } | 113 | } |
@@ -388,12 +388,6 @@ class AlarmRuleState { | @@ -388,12 +388,6 @@ class AlarmRuleState { | ||
388 | EntityKeyValue ekv = null; | 388 | EntityKeyValue ekv = null; |
389 | if (value.getDynamicValue() != null) { | 389 | if (value.getDynamicValue() != null) { |
390 | switch (value.getDynamicValue().getSourceType()) { | 390 | switch (value.getDynamicValue().getSourceType()) { |
391 | - case CURRENT_TENANT: | ||
392 | - ekv = dynamicPredicateValueCtx.getTenantValue(value.getDynamicValue().getSourceAttribute()); | ||
393 | - break; | ||
394 | - case CURRENT_CUSTOMER: | ||
395 | - ekv = dynamicPredicateValueCtx.getCustomerValue(value.getDynamicValue().getSourceAttribute()); | ||
396 | - break; | ||
397 | case CURRENT_DEVICE: | 391 | case CURRENT_DEVICE: |
398 | ekv = data.getValue(new EntityKey(EntityKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); | 392 | ekv = data.getValue(new EntityKey(EntityKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); |
399 | if (ekv == null) { | 393 | if (ekv == null) { |
@@ -405,6 +399,16 @@ class AlarmRuleState { | @@ -405,6 +399,16 @@ class AlarmRuleState { | ||
405 | } | 399 | } |
406 | } | 400 | } |
407 | } | 401 | } |
402 | + if(ekv != null || !value.getDynamicValue().isInherit()) { | ||
403 | + break; | ||
404 | + } | ||
405 | + case CURRENT_CUSTOMER: | ||
406 | + ekv = dynamicPredicateValueCtx.getCustomerValue(value.getDynamicValue().getSourceAttribute()); | ||
407 | + if(ekv != null || !value.getDynamicValue().isInherit()) { | ||
408 | + break; | ||
409 | + } | ||
410 | + case CURRENT_TENANT: | ||
411 | + ekv = dynamicPredicateValueCtx.getTenantValue(value.getDynamicValue().getSourceAttribute()); | ||
408 | } | 412 | } |
409 | } | 413 | } |
410 | return ekv; | 414 | return ekv; |
@@ -434,11 +434,152 @@ public class TbDeviceProfileNodeTest { | @@ -434,11 +434,152 @@ public class TbDeviceProfileNodeTest { | ||
434 | verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); | 434 | verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); |
435 | } | 435 | } |
436 | 436 | ||
437 | - private void init() throws TbNodeException { | 437 | + @Test |
438 | + public void testTenantInheritModeForDynamicValues() throws Exception { | ||
439 | + init(); | ||
440 | + | ||
441 | + DeviceProfile deviceProfile = new DeviceProfile(); | ||
442 | + DeviceProfileData deviceProfileData = new DeviceProfileData(); | ||
443 | + | ||
444 | + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( | ||
445 | + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "tenantAttribute" | ||
446 | + ); | ||
447 | + | ||
448 | + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); | ||
449 | + attributeKvEntity.setId(compositeKey); | ||
450 | + attributeKvEntity.setLongValue(100L); | ||
451 | + attributeKvEntity.setLastUpdateTs(0L); | ||
452 | + | ||
453 | + AttributeKvEntry entry = attributeKvEntity.toData(); | ||
454 | + ListenableFuture<List<AttributeKvEntry>> listListenableFutureWithLess = | ||
455 | + Futures.immediateFuture(Collections.singletonList(entry)); | ||
456 | + | ||
457 | + KeyFilter lowTempFilter = new KeyFilter(); | ||
458 | + lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); | ||
459 | + lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); | ||
460 | + NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); | ||
461 | + lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); | ||
462 | + lowTempPredicate.setValue( | ||
463 | + new FilterPredicateValue<>( | ||
464 | + 0.0, | ||
465 | + null, | ||
466 | + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "tenantAttribute", true)) | ||
467 | + ); | ||
468 | + lowTempFilter.setPredicate(lowTempPredicate); | ||
469 | + AlarmCondition alarmCondition = new AlarmCondition(); | ||
470 | + alarmCondition.setCondition(Collections.singletonList(lowTempFilter)); | ||
471 | + AlarmRule alarmRule = new AlarmRule(); | ||
472 | + alarmRule.setCondition(alarmCondition); | ||
473 | + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); | ||
474 | + dpa.setId("lesstempID"); | ||
475 | + dpa.setAlarmType("lessTemperatureAlarm"); | ||
476 | + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); | ||
477 | + | ||
478 | + deviceProfileData.setAlarms(Collections.singletonList(dpa)); | ||
479 | + deviceProfile.setProfileData(deviceProfileData); | ||
438 | 480 | ||
439 | - UUID uuid = new UUID(6041557255264276971L, -9019477126543226049L); | ||
440 | - System.out.println(uuid); | 481 | + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); |
482 | + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) | ||
483 | + .thenReturn(Futures.immediateFuture(Collections.emptyList())); | ||
484 | + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm")) | ||
485 | + .thenReturn(Futures.immediateFuture(null)); | ||
486 | + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())) | ||
487 | + .thenAnswer(AdditionalAnswers.returnsFirstArg()); | ||
488 | + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); | ||
489 | + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) | ||
490 | + .thenReturn(listListenableFutureWithLess); | ||
491 | + | ||
492 | + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); | ||
493 | + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) | ||
494 | + .thenReturn(theMsg); | ||
495 | + | ||
496 | + ObjectNode data = mapper.createObjectNode(); | ||
497 | + data.put("temperature", 150L); | ||
498 | + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), | ||
499 | + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); | ||
500 | + | ||
501 | + node.onMsg(ctx, msg); | ||
502 | + verify(ctx).tellSuccess(msg); | ||
503 | + verify(ctx).tellNext(theMsg, "Alarm Created"); | ||
504 | + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); | ||
505 | + | ||
506 | + } | ||
507 | + | ||
508 | + @Test | ||
509 | + public void testCustomerInheritModeForDynamicValues() throws Exception { | ||
510 | + init(); | ||
441 | 511 | ||
512 | + DeviceProfile deviceProfile = new DeviceProfile(); | ||
513 | + DeviceProfileData deviceProfileData = new DeviceProfileData(); | ||
514 | + | ||
515 | + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( | ||
516 | + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "customerAttribute" | ||
517 | + ); | ||
518 | + | ||
519 | + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); | ||
520 | + attributeKvEntity.setId(compositeKey); | ||
521 | + attributeKvEntity.setLongValue(100L); | ||
522 | + attributeKvEntity.setLastUpdateTs(0L); | ||
523 | + | ||
524 | + AttributeKvEntry entry = attributeKvEntity.toData(); | ||
525 | + ListenableFuture<List<AttributeKvEntry>> listListenableFutureWithLess = | ||
526 | + Futures.immediateFuture(Collections.singletonList(entry)); | ||
527 | + ListenableFuture<Optional<AttributeKvEntry>> optionalListenableFutureWithLess = | ||
528 | + Futures.immediateFuture(Optional.of(entry)); | ||
529 | + | ||
530 | + KeyFilter lowTempFilter = new KeyFilter(); | ||
531 | + lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); | ||
532 | + lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); | ||
533 | + NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); | ||
534 | + lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); | ||
535 | + lowTempPredicate.setValue( | ||
536 | + new FilterPredicateValue<>( | ||
537 | + 0.0, | ||
538 | + null, | ||
539 | + new DynamicValue<>(DynamicValueSourceType.CURRENT_CUSTOMER, "customerAttribute", true)) | ||
540 | + ); | ||
541 | + lowTempFilter.setPredicate(lowTempPredicate); | ||
542 | + AlarmCondition alarmCondition = new AlarmCondition(); | ||
543 | + alarmCondition.setCondition(Collections.singletonList(lowTempFilter)); | ||
544 | + AlarmRule alarmRule = new AlarmRule(); | ||
545 | + alarmRule.setCondition(alarmCondition); | ||
546 | + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); | ||
547 | + dpa.setId("lesstempID"); | ||
548 | + dpa.setAlarmType("lessTemperatureAlarm"); | ||
549 | + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); | ||
550 | + | ||
551 | + deviceProfileData.setAlarms(Collections.singletonList(dpa)); | ||
552 | + deviceProfile.setProfileData(deviceProfileData); | ||
553 | + | ||
554 | + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); | ||
555 | + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) | ||
556 | + .thenReturn(Futures.immediateFuture(Collections.emptyList())); | ||
557 | + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm")) | ||
558 | + .thenReturn(Futures.immediateFuture(null)); | ||
559 | + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())) | ||
560 | + .thenAnswer(AdditionalAnswers.returnsFirstArg()); | ||
561 | + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); | ||
562 | + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) | ||
563 | + .thenReturn(listListenableFutureWithLess); | ||
564 | + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) | ||
565 | + .thenReturn(optionalListenableFutureWithLess); | ||
566 | + | ||
567 | + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); | ||
568 | + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) | ||
569 | + .thenReturn(theMsg); | ||
570 | + | ||
571 | + ObjectNode data = mapper.createObjectNode(); | ||
572 | + data.put("temperature", 150L); | ||
573 | + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), | ||
574 | + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); | ||
575 | + | ||
576 | + node.onMsg(ctx, msg); | ||
577 | + verify(ctx).tellSuccess(msg); | ||
578 | + verify(ctx).tellNext(theMsg, "Alarm Created"); | ||
579 | + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); | ||
580 | + } | ||
581 | + | ||
582 | + private void init() throws TbNodeException { | ||
442 | Mockito.when(ctx.getTenantId()).thenReturn(tenantId); | 583 | Mockito.when(ctx.getTenantId()).thenReturn(tenantId); |
443 | Mockito.when(ctx.getDeviceProfileCache()).thenReturn(cache); | 584 | Mockito.when(ctx.getDeviceProfileCache()).thenReturn(cache); |
444 | Mockito.when(ctx.getTimeseriesService()).thenReturn(timeseriesService); | 585 | Mockito.when(ctx.getTimeseriesService()).thenReturn(timeseriesService); |
@@ -43,6 +43,7 @@ import { | @@ -43,6 +43,7 @@ import { | ||
43 | AlarmDetailsDialogComponent, | 43 | AlarmDetailsDialogComponent, |
44 | AlarmDetailsDialogData | 44 | AlarmDetailsDialogData |
45 | } from '@home/components/alarm/alarm-details-dialog.component'; | 45 | } from '@home/components/alarm/alarm-details-dialog.component'; |
46 | +import { DAY, historyInterval } from '@shared/models/time/time.models'; | ||
46 | 47 | ||
47 | export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> { | 48 | export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> { |
48 | 49 | ||
@@ -59,6 +60,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> | @@ -59,6 +60,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> | ||
59 | this.loadDataOnInit = false; | 60 | this.loadDataOnInit = false; |
60 | this.tableTitle = ''; | 61 | this.tableTitle = ''; |
61 | this.useTimePageLink = true; | 62 | this.useTimePageLink = true; |
63 | + this.defaultTimewindowInterval = historyInterval(DAY * 30); | ||
62 | this.detailsPanelEnabled = false; | 64 | this.detailsPanelEnabled = false; |
63 | this.selectionEnabled = false; | 65 | this.selectionEnabled = false; |
64 | this.searchEnabled = true; | 66 | this.searchEnabled = true; |
@@ -55,11 +55,11 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models'; | @@ -55,11 +55,11 @@ 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 { DAY, historyInterval, HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; | 58 | +import { HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; |
59 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; | 59 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; |
60 | import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; | 60 | import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; |
61 | import { isDefined, isUndefined } from '@core/utils'; | 61 | import { isDefined, isUndefined } from '@core/utils'; |
62 | -import { HasUUID } from '../../../../shared/models/id/has-uuid'; | 62 | +import { HasUUID } from '@shared/models/id/has-uuid'; |
63 | 63 | ||
64 | @Component({ | 64 | @Component({ |
65 | selector: 'tb-entities-table', | 65 | selector: 'tb-entities-table', |
@@ -202,7 +202,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -202,7 +202,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
202 | this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; | 202 | this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; |
203 | 203 | ||
204 | if (this.entitiesTableConfig.useTimePageLink) { | 204 | if (this.entitiesTableConfig.useTimePageLink) { |
205 | - this.timewindow = historyInterval(DAY); | 205 | + this.timewindow = this.entitiesTableConfig.defaultTimewindowInterval; |
206 | const currentTime = Date.now(); | 206 | const currentTime = Date.now(); |
207 | this.pageLink = new TimePageLink(10, 0, null, sortOrder, | 207 | this.pageLink = new TimePageLink(10, 0, null, sortOrder, |
208 | currentTime - this.timewindow.history.timewindowMs, currentTime); | 208 | currentTime - this.timewindow.history.timewindowMs, currentTime); |
@@ -446,7 +446,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -446,7 +446,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
446 | resetSortAndFilter(update: boolean = true, preserveTimewindow: boolean = false) { | 446 | resetSortAndFilter(update: boolean = true, preserveTimewindow: boolean = false) { |
447 | this.pageLink.textSearch = null; | 447 | this.pageLink.textSearch = null; |
448 | if (this.entitiesTableConfig.useTimePageLink && !preserveTimewindow) { | 448 | if (this.entitiesTableConfig.useTimePageLink && !preserveTimewindow) { |
449 | - this.timewindow = historyInterval(DAY); | 449 | + this.timewindow = this.entitiesTableConfig.defaultTimewindowInterval; |
450 | } | 450 | } |
451 | if (this.displayPagination) { | 451 | if (this.displayPagination) { |
452 | this.paginator.pageIndex = 0; | 452 | this.paginator.pageIndex = 0; |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <div fxFlex fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="8px" [formGroup]="booleanFilterPredicateFormGroup"> | 18 | <div fxFlex fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="8px" [formGroup]="booleanFilterPredicateFormGroup"> |
19 | - <mat-form-field floatLabel="always" hideRequiredMarker fxFlex="40" class="mat-block"> | 19 | + <mat-form-field floatLabel="always" hideRequiredMarker fxFlex="30" class="mat-block"> |
20 | <mat-label></mat-label> | 20 | <mat-label></mat-label> |
21 | <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> | 21 | <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> |
22 | <mat-option *ngFor="let operation of booleanOperations" [value]="operation"> | 22 | <mat-option *ngFor="let operation of booleanOperations" [value]="operation"> |
@@ -25,7 +25,7 @@ | @@ -25,7 +25,7 @@ | ||
25 | </mat-select> | 25 | </mat-select> |
26 | </mat-form-field> | 26 | </mat-form-field> |
27 | <tb-filter-predicate-value [allowUserDynamicSource]="allowUserDynamicSource" | 27 | <tb-filter-predicate-value [allowUserDynamicSource]="allowUserDynamicSource" |
28 | - fxFlex="60" | 28 | + fxFlex="70" |
29 | [valueType]="valueTypeEnum.BOOLEAN" | 29 | [valueType]="valueTypeEnum.BOOLEAN" |
30 | formControlName="value"> | 30 | formControlName="value"> |
31 | </tb-filter-predicate-value> | 31 | </tb-filter-predicate-value> |
@@ -26,12 +26,12 @@ | @@ -26,12 +26,12 @@ | ||
26 | <span fxFlex="8"></span> | 26 | <span fxFlex="8"></span> |
27 | <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex="92"> | 27 | <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex="92"> |
28 | <div fxFlex fxLayout="row" fxLayoutGap="8px"> | 28 | <div fxFlex fxLayout="row" fxLayoutGap="8px"> |
29 | - <div fxFlex="40" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> | 29 | + <div fxFlex="30" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
30 | <label fxFlex translate class="tb-title no-padding">filter.operation.operation</label> | 30 | <label fxFlex translate class="tb-title no-padding">filter.operation.operation</label> |
31 | <label *ngIf="valueType === valueTypeEnum.STRING" | 31 | <label *ngIf="valueType === valueTypeEnum.STRING" |
32 | translate class="tb-title no-padding" style="min-width: 70px;">filter.ignore-case</label> | 32 | translate class="tb-title no-padding" style="min-width: 70px;">filter.ignore-case</label> |
33 | </div> | 33 | </div> |
34 | - <label fxFlex="60" translate class="tb-title no-padding">filter.value</label> | 34 | + <label fxFlex="70" translate class="tb-title no-padding">filter.value</label> |
35 | </div> | 35 | </div> |
36 | <label *ngIf="displayUserParameters" | 36 | <label *ngIf="displayUserParameters" |
37 | translate class="tb-title no-padding" style="width: 60px;">filter.user-parameters</label> | 37 | translate class="tb-title no-padding" style="width: 60px;">filter.user-parameters</label> |
@@ -47,7 +47,7 @@ | @@ -47,7 +47,7 @@ | ||
47 | </div> | 47 | </div> |
48 | <div fxFlex fxLayout="column" [fxShow]="dynamicMode"> | 48 | <div fxFlex fxLayout="column" [fxShow]="dynamicMode"> |
49 | <div formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> | 49 | <div formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
50 | - <div fxFlex fxLayout="column"> | 50 | + <div fxFlex="35" fxLayout="column"> |
51 | <mat-form-field floatLabel="always" hideRequiredMarker class="mat-block"> | 51 | <mat-form-field floatLabel="always" hideRequiredMarker class="mat-block"> |
52 | <mat-label></mat-label> | 52 | <mat-label></mat-label> |
53 | <mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}"> | 53 | <mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}"> |
@@ -68,6 +68,14 @@ | @@ -68,6 +68,14 @@ | ||
68 | </mat-form-field> | 68 | </mat-form-field> |
69 | <div class="tb-hint" translate>filter.source-attribute</div> | 69 | <div class="tb-hint" translate>filter.source-attribute</div> |
70 | </div> | 70 | </div> |
71 | + <div *ngIf="!allow && inheritMode" | ||
72 | + fxLayout="column" | ||
73 | + style="padding-top: 6px"> | ||
74 | + <mat-checkbox formControlName="inherit"> | ||
75 | + {{ 'filter.inherit-owner' | translate}} | ||
76 | + </mat-checkbox> | ||
77 | + <div class="tb-hint" translate>filter.source-attribute-not-set</div> | ||
78 | + </div> | ||
71 | </div> | 79 | </div> |
72 | </div> | 80 | </div> |
73 | <button mat-icon-button | 81 | <button mat-icon-button |
@@ -44,6 +44,10 @@ import { | @@ -44,6 +44,10 @@ import { | ||
44 | }) | 44 | }) |
45 | export class FilterPredicateValueComponent implements ControlValueAccessor, OnInit { | 45 | export class FilterPredicateValueComponent implements ControlValueAccessor, OnInit { |
46 | 46 | ||
47 | + private readonly inheritModeForSources: DynamicValueSourceType[] = [ | ||
48 | + DynamicValueSourceType.CURRENT_CUSTOMER, | ||
49 | + DynamicValueSourceType.CURRENT_DEVICE]; | ||
50 | + | ||
47 | @Input() disabled: boolean; | 51 | @Input() disabled: boolean; |
48 | 52 | ||
49 | @Input() | 53 | @Input() |
@@ -72,6 +76,8 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | @@ -72,6 +76,8 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | ||
72 | 76 | ||
73 | dynamicMode = false; | 77 | dynamicMode = false; |
74 | 78 | ||
79 | + inheritMode = false; | ||
80 | + | ||
75 | allow = true; | 81 | allow = true; |
76 | 82 | ||
77 | private propagateChange = null; | 83 | private propagateChange = null; |
@@ -105,7 +111,8 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | @@ -105,7 +111,8 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | ||
105 | dynamicValue: this.fb.group( | 111 | dynamicValue: this.fb.group( |
106 | { | 112 | { |
107 | sourceType: [null], | 113 | sourceType: [null], |
108 | - sourceAttribute: [null] | 114 | + sourceAttribute: [null], |
115 | + inherit: [false] | ||
109 | } | 116 | } |
110 | ) | 117 | ) |
111 | }); | 118 | }); |
@@ -114,6 +121,7 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | @@ -114,6 +121,7 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | ||
114 | if (!sourceType) { | 121 | if (!sourceType) { |
115 | this.filterPredicateValueFormGroup.get('dynamicValue').get('sourceAttribute').patchValue(null, {emitEvent: false}); | 122 | this.filterPredicateValueFormGroup.get('dynamicValue').get('sourceAttribute').patchValue(null, {emitEvent: false}); |
116 | } | 123 | } |
124 | + this.updateShowInheritMode(sourceType); | ||
117 | } | 125 | } |
118 | ); | 126 | ); |
119 | this.filterPredicateValueFormGroup.valueChanges.subscribe(() => { | 127 | this.filterPredicateValueFormGroup.valueChanges.subscribe(() => { |
@@ -139,10 +147,13 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | @@ -139,10 +147,13 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | ||
139 | 147 | ||
140 | writeValue(predicateValue: FilterPredicateValue<string | number | boolean>): void { | 148 | writeValue(predicateValue: FilterPredicateValue<string | number | boolean>): void { |
141 | this.filterPredicateValueFormGroup.get('defaultValue').patchValue(predicateValue.defaultValue, {emitEvent: false}); | 149 | this.filterPredicateValueFormGroup.get('defaultValue').patchValue(predicateValue.defaultValue, {emitEvent: false}); |
142 | - this.filterPredicateValueFormGroup.get('dynamicValue').get('sourceType').patchValue(predicateValue.dynamicValue ? | 150 | + this.filterPredicateValueFormGroup.get('dynamicValue.sourceType').patchValue(predicateValue.dynamicValue ? |
143 | predicateValue.dynamicValue.sourceType : null, {emitEvent: false}); | 151 | predicateValue.dynamicValue.sourceType : null, {emitEvent: false}); |
144 | - this.filterPredicateValueFormGroup.get('dynamicValue').get('sourceAttribute').patchValue(predicateValue.dynamicValue ? | 152 | + this.filterPredicateValueFormGroup.get('dynamicValue.sourceAttribute').patchValue(predicateValue.dynamicValue ? |
145 | predicateValue.dynamicValue.sourceAttribute : null, {emitEvent: false}); | 153 | predicateValue.dynamicValue.sourceAttribute : null, {emitEvent: false}); |
154 | + this.filterPredicateValueFormGroup.get('dynamicValue.inherit').patchValue(predicateValue.dynamicValue ? | ||
155 | + predicateValue.dynamicValue.inherit : false, {emitEvent: false}); | ||
156 | + this.updateShowInheritMode(predicateValue?.dynamicValue?.sourceType); | ||
146 | } | 157 | } |
147 | 158 | ||
148 | private updateModel() { | 159 | private updateModel() { |
@@ -158,4 +169,12 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | @@ -158,4 +169,12 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn | ||
158 | this.propagateChange(predicateValue); | 169 | this.propagateChange(predicateValue); |
159 | } | 170 | } |
160 | 171 | ||
172 | + private updateShowInheritMode(sourceType: DynamicValueSourceType) { | ||
173 | + if (this.inheritModeForSources.includes(sourceType)) { | ||
174 | + this.inheritMode = true; | ||
175 | + } else { | ||
176 | + this.filterPredicateValueFormGroup.get('dynamicValue.inherit').patchValue(false, {emitEvent: false}); | ||
177 | + this.inheritMode = false; | ||
178 | + } | ||
179 | + } | ||
161 | } | 180 | } |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <div fxFlex fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="8px" [formGroup]="numericFilterPredicateFormGroup"> | 18 | <div fxFlex fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="8px" [formGroup]="numericFilterPredicateFormGroup"> |
19 | - <mat-form-field floatLabel="always" hideRequiredMarker fxFlex="40" class="mat-block"> | 19 | + <mat-form-field floatLabel="always" hideRequiredMarker fxFlex="30" class="mat-block"> |
20 | <mat-label></mat-label> | 20 | <mat-label></mat-label> |
21 | <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> | 21 | <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> |
22 | <mat-option *ngFor="let operation of numericOperations" [value]="operation"> | 22 | <mat-option *ngFor="let operation of numericOperations" [value]="operation"> |
@@ -25,7 +25,7 @@ | @@ -25,7 +25,7 @@ | ||
25 | </mat-select> | 25 | </mat-select> |
26 | </mat-form-field> | 26 | </mat-form-field> |
27 | <tb-filter-predicate-value [allowUserDynamicSource]="allowUserDynamicSource" | 27 | <tb-filter-predicate-value [allowUserDynamicSource]="allowUserDynamicSource" |
28 | - fxFlex="60" | 28 | + fxFlex="70" |
29 | [valueType]="valueType" | 29 | [valueType]="valueType" |
30 | formControlName="value"> | 30 | formControlName="value"> |
31 | </tb-filter-predicate-value> | 31 | </tb-filter-predicate-value> |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <div fxFlex fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="8px" [formGroup]="stringFilterPredicateFormGroup"> | 18 | <div fxFlex fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="8px" [formGroup]="stringFilterPredicateFormGroup"> |
19 | - <div fxFlex="40" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> | 19 | + <div fxFlex="30" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
20 | <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> | 20 | <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block"> |
21 | <mat-label></mat-label> | 21 | <mat-label></mat-label> |
22 | <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> | 22 | <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}"> |
@@ -29,7 +29,7 @@ | @@ -29,7 +29,7 @@ | ||
29 | </mat-checkbox> | 29 | </mat-checkbox> |
30 | </div> | 30 | </div> |
31 | <tb-filter-predicate-value [allowUserDynamicSource]="allowUserDynamicSource" | 31 | <tb-filter-predicate-value [allowUserDynamicSource]="allowUserDynamicSource" |
32 | - fxFlex="60" | 32 | + fxFlex="70" |
33 | [valueType]="valueTypeEnum.STRING" | 33 | [valueType]="valueTypeEnum.STRING" |
34 | formControlName="value"> | 34 | formControlName="value"> |
35 | </tb-filter-predicate-value> | 35 | </tb-filter-predicate-value> |
@@ -30,6 +30,7 @@ import { EntitiesTableComponent } from '@home/components/entity/entities-table.c | @@ -30,6 +30,7 @@ import { EntitiesTableComponent } from '@home/components/entity/entities-table.c | ||
30 | import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; | 30 | import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; |
31 | import { ActivatedRoute } from '@angular/router'; | 31 | import { ActivatedRoute } from '@angular/router'; |
32 | import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; | 32 | import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; |
33 | +import { DAY, historyInterval } from '@shared/models/time/time.models'; | ||
33 | 34 | ||
34 | export type EntityBooleanFunction<T extends BaseData<HasId>> = (entity: T) => boolean; | 35 | export type EntityBooleanFunction<T extends BaseData<HasId>> = (entity: T) => boolean; |
35 | export type EntityStringFunction<T extends BaseData<HasId>> = (entity: T) => string; | 36 | export type EntityStringFunction<T extends BaseData<HasId>> = (entity: T) => string; |
@@ -135,6 +136,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | @@ -135,6 +136,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | ||
135 | onLoadAction: (route: ActivatedRoute) => void = null; | 136 | onLoadAction: (route: ActivatedRoute) => void = null; |
136 | table: EntitiesTableComponent = null; | 137 | table: EntitiesTableComponent = null; |
137 | useTimePageLink = false; | 138 | useTimePageLink = false; |
139 | + defaultTimewindowInterval = historyInterval(DAY); | ||
138 | entityType: EntityType = null; | 140 | entityType: EntityType = null; |
139 | tableTitle = ''; | 141 | tableTitle = ''; |
140 | selectionEnabled = true; | 142 | selectionEnabled = true; |
@@ -162,7 +164,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | @@ -162,7 +164,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | ||
162 | dataSource: (dataLoadedFunction: (col?: number, row?: number) => void) | 164 | dataSource: (dataLoadedFunction: (col?: number, row?: number) => void) |
163 | => EntitiesDataSource<L> = (dataLoadedFunction: (col?: number, row?: number) => void) => { | 165 | => EntitiesDataSource<L> = (dataLoadedFunction: (col?: number, row?: number) => void) => { |
164 | return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction); | 166 | return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction); |
165 | - }; | 167 | + } |
166 | detailsReadonly: EntityBooleanFunction<T> = () => false; | 168 | detailsReadonly: EntityBooleanFunction<T> = () => false; |
167 | entitySelectionEnabled: EntityBooleanFunction<L> = () => true; | 169 | entitySelectionEnabled: EntityBooleanFunction<L> = () => true; |
168 | deleteEnabled: EntityBooleanFunction<T | L> = () => true; | 170 | deleteEnabled: EntityBooleanFunction<T | L> = () => true; |
@@ -285,6 +285,7 @@ export const dynamicValueSourceTypeTranslationMap = new Map<DynamicValueSourceTy | @@ -285,6 +285,7 @@ export const dynamicValueSourceTypeTranslationMap = new Map<DynamicValueSourceTy | ||
285 | export interface DynamicValue<T> { | 285 | export interface DynamicValue<T> { |
286 | sourceType: DynamicValueSourceType; | 286 | sourceType: DynamicValueSourceType; |
287 | sourceAttribute: string; | 287 | sourceAttribute: string; |
288 | + inherit?: boolean; | ||
288 | } | 289 | } |
289 | 290 | ||
290 | export interface FilterPredicateValue<T> { | 291 | export interface FilterPredicateValue<T> { |
@@ -1591,7 +1591,9 @@ | @@ -1591,7 +1591,9 @@ | ||
1591 | "no-dynamic-value": "No dynamic value", | 1591 | "no-dynamic-value": "No dynamic value", |
1592 | "source-attribute": "Source attribute", | 1592 | "source-attribute": "Source attribute", |
1593 | "switch-to-dynamic-value": "Switch to dynamic value", | 1593 | "switch-to-dynamic-value": "Switch to dynamic value", |
1594 | - "switch-to-default-value": "Switch to default value" | 1594 | + "switch-to-default-value": "Switch to default value", |
1595 | + "inherit-owner": "Inherit from owner", | ||
1596 | + "source-attribute-not-set": "If source attribute isn't set" | ||
1595 | }, | 1597 | }, |
1596 | "fullscreen": { | 1598 | "fullscreen": { |
1597 | "expand": "Expand to fullscreen", | 1599 | "expand": "Expand to fullscreen", |