Commit a14c10f1bcde77e9e161930eb87daed77b00bec6

Authored by Andrii Shvaika
2 parents bfd0ed5d c8d8321f

Merge branch 'master' of github.com:thingsboard/thingsboard

@@ -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",