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