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
18 18 import com.fasterxml.jackson.annotation.JsonIgnore;
19 19 import lombok.Data;
20   -import lombok.Getter;
  20 +import lombok.RequiredArgsConstructor;
21 21
22 22 @Data
  23 +@RequiredArgsConstructor
23 24 public class DynamicValue<T> {
24 25
25 26 @JsonIgnore
26 27 private T resolvedValue;
27 28
28   - @Getter
29 29 private final DynamicValueSourceType sourceType;
30   - @Getter
31 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 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 768 if (log.isTraceEnabled()) {
769 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 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 780 TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) {
781 781 DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
782 782 DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId);
... ...
... ... @@ -104,9 +104,10 @@ public class TbMsgGeneratorNode implements TbNode {
104 104 }
105 105 },
106 106 t -> {
107   - if (initialized) {
  107 + if (initialized && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) {
108 108 ctx.tellFailure(msg, t);
109 109 scheduleTickMsg(ctx);
  110 + currentMsgCount++;
110 111 }
111 112 });
112 113 }
... ...
... ... @@ -388,12 +388,6 @@ class AlarmRuleState {
388 388 EntityKeyValue ekv = null;
389 389 if (value.getDynamicValue() != null) {
390 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 391 case CURRENT_DEVICE:
398 392 ekv = data.getValue(new EntityKey(EntityKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute()));
399 393 if (ekv == null) {
... ... @@ -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 414 return ekv;
... ...
... ... @@ -434,11 +434,152 @@ public class TbDeviceProfileNodeTest {
434 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 583 Mockito.when(ctx.getTenantId()).thenReturn(tenantId);
443 584 Mockito.when(ctx.getDeviceProfileCache()).thenReturn(cache);
444 585 Mockito.when(ctx.getTimeseriesService()).thenReturn(timeseriesService);
... ...
... ... @@ -43,6 +43,7 @@ import {
43 43 AlarmDetailsDialogComponent,
44 44 AlarmDetailsDialogData
45 45 } from '@home/components/alarm/alarm-details-dialog.component';
  46 +import { DAY, historyInterval } from '@shared/models/time/time.models';
46 47
47 48 export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> {
48 49
... ... @@ -59,6 +60,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
59 60 this.loadDataOnInit = false;
60 61 this.tableTitle = '';
61 62 this.useTimePageLink = true;
  63 + this.defaultTimewindowInterval = historyInterval(DAY * 30);
62 64 this.detailsPanelEnabled = false;
63 65 this.selectionEnabled = false;
64 66 this.searchEnabled = true;
... ...
... ... @@ -55,11 +55,11 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models';
55 55 import { DialogService } from '@core/services/dialog.service';
56 56 import { AddEntityDialogComponent } from './add-entity-dialog.component';
57 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 59 import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
60 60 import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
61 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 64 @Component({
65 65 selector: 'tb-entities-table',
... ... @@ -202,7 +202,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
202 202 this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
203 203
204 204 if (this.entitiesTableConfig.useTimePageLink) {
205   - this.timewindow = historyInterval(DAY);
  205 + this.timewindow = this.entitiesTableConfig.defaultTimewindowInterval;
206 206 const currentTime = Date.now();
207 207 this.pageLink = new TimePageLink(10, 0, null, sortOrder,
208 208 currentTime - this.timewindow.history.timewindowMs, currentTime);
... ... @@ -446,7 +446,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
446 446 resetSortAndFilter(update: boolean = true, preserveTimewindow: boolean = false) {
447 447 this.pageLink.textSearch = null;
448 448 if (this.entitiesTableConfig.useTimePageLink && !preserveTimewindow) {
449   - this.timewindow = historyInterval(DAY);
  449 + this.timewindow = this.entitiesTableConfig.defaultTimewindowInterval;
450 450 }
451 451 if (this.displayPagination) {
452 452 this.paginator.pageIndex = 0;
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 -->
18 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 20 <mat-label></mat-label>
21 21 <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
22 22 <mat-option *ngFor="let operation of booleanOperations" [value]="operation">
... ... @@ -25,7 +25,7 @@
25 25 </mat-select>
26 26 </mat-form-field>
27 27 <tb-filter-predicate-value [allowUserDynamicSource]="allowUserDynamicSource"
28   - fxFlex="60"
  28 + fxFlex="70"
29 29 [valueType]="valueTypeEnum.BOOLEAN"
30 30 formControlName="value">
31 31 </tb-filter-predicate-value>
... ...
... ... @@ -26,12 +26,12 @@
26 26 <span fxFlex="8"></span>
27 27 <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex="92">
28 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 30 <label fxFlex translate class="tb-title no-padding">filter.operation.operation</label>
31 31 <label *ngIf="valueType === valueTypeEnum.STRING"
32 32 translate class="tb-title no-padding" style="min-width: 70px;">filter.ignore-case</label>
33 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 35 </div>
36 36 <label *ngIf="displayUserParameters"
37 37 translate class="tb-title no-padding" style="width: 60px;">filter.user-parameters</label>
... ...
... ... @@ -47,7 +47,7 @@
47 47 </div>
48 48 <div fxFlex fxLayout="column" [fxShow]="dynamicMode">
49 49 <div formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
50   - <div fxFlex fxLayout="column">
  50 + <div fxFlex="35" fxLayout="column">
51 51 <mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
52 52 <mat-label></mat-label>
53 53 <mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
... ... @@ -68,6 +68,14 @@
68 68 </mat-form-field>
69 69 <div class="tb-hint" translate>filter.source-attribute</div>
70 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 79 </div>
72 80 </div>
73 81 <button mat-icon-button
... ...
... ... @@ -44,6 +44,10 @@ import {
44 44 })
45 45 export class FilterPredicateValueComponent implements ControlValueAccessor, OnInit {
46 46
  47 + private readonly inheritModeForSources: DynamicValueSourceType[] = [
  48 + DynamicValueSourceType.CURRENT_CUSTOMER,
  49 + DynamicValueSourceType.CURRENT_DEVICE];
  50 +
47 51 @Input() disabled: boolean;
48 52
49 53 @Input()
... ... @@ -72,6 +76,8 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
72 76
73 77 dynamicMode = false;
74 78
  79 + inheritMode = false;
  80 +
75 81 allow = true;
76 82
77 83 private propagateChange = null;
... ... @@ -105,7 +111,8 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
105 111 dynamicValue: this.fb.group(
106 112 {
107 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 121 if (!sourceType) {
115 122 this.filterPredicateValueFormGroup.get('dynamicValue').get('sourceAttribute').patchValue(null, {emitEvent: false});
116 123 }
  124 + this.updateShowInheritMode(sourceType);
117 125 }
118 126 );
119 127 this.filterPredicateValueFormGroup.valueChanges.subscribe(() => {
... ... @@ -139,10 +147,13 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
139 147
140 148 writeValue(predicateValue: FilterPredicateValue<string | number | boolean>): void {
141 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 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 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 159 private updateModel() {
... ... @@ -158,4 +169,12 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
158 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 16
17 17 -->
18 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 20 <mat-label></mat-label>
21 21 <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
22 22 <mat-option *ngFor="let operation of numericOperations" [value]="operation">
... ... @@ -25,7 +25,7 @@
25 25 </mat-select>
26 26 </mat-form-field>
27 27 <tb-filter-predicate-value [allowUserDynamicSource]="allowUserDynamicSource"
28   - fxFlex="60"
  28 + fxFlex="70"
29 29 [valueType]="valueType"
30 30 formControlName="value">
31 31 </tb-filter-predicate-value>
... ...
... ... @@ -16,7 +16,7 @@
16 16
17 17 -->
18 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 20 <mat-form-field floatLabel="always" hideRequiredMarker fxFlex class="mat-block">
21 21 <mat-label></mat-label>
22 22 <mat-select required formControlName="operation" placeholder="{{'filter.operation.operation' | translate}}">
... ... @@ -29,7 +29,7 @@
29 29 </mat-checkbox>
30 30 </div>
31 31 <tb-filter-predicate-value [allowUserDynamicSource]="allowUserDynamicSource"
32   - fxFlex="60"
  32 + fxFlex="70"
33 33 [valueType]="valueTypeEnum.STRING"
34 34 formControlName="value">
35 35 </tb-filter-predicate-value>
... ...
... ... @@ -30,6 +30,7 @@ import { EntitiesTableComponent } from '@home/components/entity/entities-table.c
30 30 import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component';
31 31 import { ActivatedRoute } from '@angular/router';
32 32 import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
  33 +import { DAY, historyInterval } from '@shared/models/time/time.models';
33 34
34 35 export type EntityBooleanFunction<T extends BaseData<HasId>> = (entity: T) => boolean;
35 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 136 onLoadAction: (route: ActivatedRoute) => void = null;
136 137 table: EntitiesTableComponent = null;
137 138 useTimePageLink = false;
  139 + defaultTimewindowInterval = historyInterval(DAY);
138 140 entityType: EntityType = null;
139 141 tableTitle = '';
140 142 selectionEnabled = true;
... ... @@ -162,7 +164,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
162 164 dataSource: (dataLoadedFunction: (col?: number, row?: number) => void)
163 165 => EntitiesDataSource<L> = (dataLoadedFunction: (col?: number, row?: number) => void) => {
164 166 return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction);
165   - };
  167 + }
166 168 detailsReadonly: EntityBooleanFunction<T> = () => false;
167 169 entitySelectionEnabled: EntityBooleanFunction<L> = () => true;
168 170 deleteEnabled: EntityBooleanFunction<T | L> = () => true;
... ...
... ... @@ -285,6 +285,7 @@ export const dynamicValueSourceTypeTranslationMap = new Map<DynamicValueSourceTy
285 285 export interface DynamicValue<T> {
286 286 sourceType: DynamicValueSourceType;
287 287 sourceAttribute: string;
  288 + inherit?: boolean;
288 289 }
289 290
290 291 export interface FilterPredicateValue<T> {
... ...
... ... @@ -1591,7 +1591,9 @@
1591 1591 "no-dynamic-value": "No dynamic value",
1592 1592 "source-attribute": "Source attribute",
1593 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 1598 "fullscreen": {
1597 1599 "expand": "Expand to fullscreen",
... ...