diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index 8c27417f90..11e0edfbe8 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.install.update; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; @@ -41,16 +43,21 @@ import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.data.query.DynamicValue; +import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Utils; import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.sql.device.DeviceProfileRepository; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.service.install.InstallScripts; @@ -92,6 +99,9 @@ public class DefaultDataUpdateService implements DataUpdateService { @Autowired private AlarmDao alarmDao; + @Autowired + private DeviceProfileRepository deviceProfileRepository; + @Autowired private OAuth2Service oAuth2Service; @@ -114,6 +124,7 @@ public class DefaultDataUpdateService implements DataUpdateService { log.info("Updating data from version 3.2.2 to 3.3.0 ..."); tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); tenantsAlarmsCustomerUpdater.updateEntities(null); + deviceProfileEntityDynamicConditionsUpdater.updateEntities(null); updateOAuth2Params(); break; default: @@ -121,6 +132,45 @@ public class DefaultDataUpdateService implements DataUpdateService { } } + private final PaginatedUpdater deviceProfileEntityDynamicConditionsUpdater = + new PaginatedUpdater<>() { + + @Override + protected String getName() { + return "Device Profile Entity Dynamic Conditions Updater"; + } + + @Override + protected PageData findEntities(String id, PageLink pageLink) { + return DaoUtil.pageToPageData(deviceProfileRepository.findAll(DaoUtil.toPageable(pageLink))); + } + + @Override + protected void updateEntity(DeviceProfileEntity deviceProfile) { + if (deviceProfile.getProfileData().has("alarms") && + !deviceProfile.getProfileData().get("alarms").isNull()) { + boolean isUpdated = false; + JsonNode array = deviceProfile.getProfileData().get("alarms"); + for (JsonNode node : array) { + if (node.has("createRules")) { + JsonNode createRules = node.get("createRules"); + for (AlarmSeverity severity : AlarmSeverity.values()) { + if (createRules.has(severity.name())) { + isUpdated = isUpdated || convertDeviceProfileAlarmRulesForVersion330(createRules.get(severity.name()).get("condition").get("spec")); + } + } + } + if (node.has("clearRule") && !node.get("clearRule").isNull()) { + isUpdated = isUpdated || convertDeviceProfileAlarmRulesForVersion330(node.get("clearRule").get("condition").get("spec")); + } + } + if (isUpdated) { + deviceProfileRepository.save(deviceProfile); + } + } + } + }; + private final PaginatedUpdater tenantsDefaultRuleChainUpdater = new PaginatedUpdater<>() { @@ -370,6 +420,33 @@ public class DefaultDataUpdateService implements DataUpdateService { } } + private boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) { + if (spec != null) { + if (spec.has("type") && spec.get("type").asText().equals("DURATION")) { + if (spec.has("value")) { + long value = spec.get("value").asLong(); + var predicate = new FilterPredicateValue<>( + value, null, new DynamicValue<>(null, null, false) + ); + ((ObjectNode) spec).remove("value"); + ((ObjectNode) spec).putPOJO("predicate", predicate); + return true; + } + } else if (spec.has("type") && spec.get("type").asText().equals("REPEATING")) { + if (spec.has("count")) { + int count = spec.get("count").asInt(); + var predicate = new FilterPredicateValue<>( + count, null, new DynamicValue<>(null, null, false) + ); + ((ObjectNode) spec).remove("count"); + ((ObjectNode) spec).putPOJO("predicate", predicate); + return true; + } + } + } + return false; + } + private void updateOAuth2Params() { try { OAuth2ClientsParams oauth2ClientsParams = oAuth2Service.findOAuth2Params(); @@ -380,9 +457,8 @@ public class DefaultDataUpdateService implements DataUpdateService { oAuth2Service.saveOAuth2Params(new OAuth2ClientsParams(false, Collections.emptyList())); log.info("Successfully updated OAuth2 parameters!"); } - } - catch (Exception e) { - log.error("Failed to update OAuth2 parameters", e); + } catch (Exception e) { + log.error("Failed to update OAuth2 parameters", e); } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java index 4bfe744863..768d627a16 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @Slf4j -public abstract class PaginatedUpdater> { +public abstract class PaginatedUpdater { private static final int DEFAULT_LIMIT = 100; private int updated = 0; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java index 7ae5b96ed8..f4f4077971 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; +import org.thingsboard.server.common.data.query.FilterPredicateValue; import java.util.concurrent.TimeUnit; @@ -25,7 +26,7 @@ import java.util.concurrent.TimeUnit; public class DurationAlarmConditionSpec implements AlarmConditionSpec { private TimeUnit unit; - private long value; + private FilterPredicateValue predicate; @Override public AlarmConditionSpecType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java index 0905ef6b94..c7060ee642 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java @@ -17,14 +17,13 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; - -import java.util.concurrent.TimeUnit; +import org.thingsboard.server.common.data.query.FilterPredicateValue; @Data @JsonIgnoreProperties(ignoreUnknown = true) public class RepeatingAlarmConditionSpec implements AlarmConditionSpec { - private int count; + private FilterPredicateValue predicate; @Override public AlarmConditionSpecType getType() { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index 928a403fc4..8ae6390b31 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.device.profile.AlarmConditionSpec; +import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule; import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem; @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; +import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate; @@ -43,6 +45,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.Function; @Data @@ -52,8 +55,6 @@ class AlarmRuleState { private final AlarmSeverity severity; private final AlarmRule alarmRule; private final AlarmConditionSpec spec; - private final long requiredDurationInMs; - private final long requiredRepeats; private final Set entityKeys; private PersistedAlarmRuleState state; private boolean updateFlag; @@ -69,20 +70,6 @@ class AlarmRuleState { this.state = new PersistedAlarmRuleState(0L, 0L, 0L); } this.spec = getSpec(alarmRule); - long requiredDurationInMs = 0; - long requiredRepeats = 0; - switch (spec.getType()) { - case DURATION: - DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec; - requiredDurationInMs = duration.getUnit().toMillis(duration.getValue()); - break; - case REPEATING: - RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec; - requiredRepeats = repeating.getCount(); - break; - } - this.requiredDurationInMs = requiredDurationInMs; - this.requiredRepeats = requiredRepeats; this.dynamicPredicateValueCtx = dynamicPredicateValueCtx; } @@ -211,6 +198,7 @@ class AlarmRuleState { if (active && eval(alarmRule.getCondition(), data)) { state.setEventCount(state.getEventCount() + 1); updateFlag = true; + long requiredRepeats = resolveRequiredRepeats(data); return state.getEventCount() >= requiredRepeats ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; } else { return AlarmEvalResult.FALSE; @@ -230,18 +218,62 @@ class AlarmRuleState { state.setDuration(0L); updateFlag = true; } + long requiredDurationInMs = resolveRequiredDurationInMs(data); return state.getDuration() > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; } else { return AlarmEvalResult.FALSE; } } - public AlarmEvalResult eval(long ts) { + private long resolveRequiredRepeats(DataSnapshot data) { + long repeatingTimes = 0; + AlarmConditionSpec alarmConditionSpec = getSpec(); + AlarmConditionSpecType specType = alarmConditionSpec.getType(); + if(specType.equals(AlarmConditionSpecType.REPEATING)) { + RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec; + + repeatingTimes = repeating.getPredicate().getDefaultValue(); + + if (repeating.getPredicate().getDynamicValue() != null && + repeating.getPredicate().getDynamicValue().getSourceAttribute() != null) { + EntityKeyValue repeatingKeyValue = getDynamicPredicateValue(data, repeating.getPredicate().getDynamicValue()); + if (repeatingKeyValue != null) { + repeatingTimes = repeatingKeyValue.getLngValue(); + } + } + } + return repeatingTimes; + } + + private long resolveRequiredDurationInMs(DataSnapshot data) { + long durationTimeInMs = 0; + AlarmConditionSpec alarmConditionSpec = getSpec(); + AlarmConditionSpecType specType = alarmConditionSpec.getType(); + if(specType.equals(AlarmConditionSpecType.DURATION)) { + DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec; + TimeUnit timeUnit = duration.getUnit(); + + durationTimeInMs = timeUnit.toMillis(duration.getPredicate().getDefaultValue()); + + if (duration.getPredicate().getDynamicValue() != null && + duration.getPredicate().getDynamicValue().getSourceAttribute() != null) { + EntityKeyValue durationKeyValue = getDynamicPredicateValue(data, duration.getPredicate().getDynamicValue()); + if (durationKeyValue != null) { + durationTimeInMs = timeUnit.toMillis(durationKeyValue.getLngValue()); + } + } + } + + return durationTimeInMs; + } + + public AlarmEvalResult eval(long ts, DataSnapshot dataSnapshot) { switch (spec.getType()) { case SIMPLE: case REPEATING: return AlarmEvalResult.NOT_YET_TRUE; case DURATION: + long requiredDurationInMs = resolveRequiredDurationInMs(dataSnapshot); if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) { long duration = state.getDuration() + (ts - state.getLastEventTs()); if (isActive(ts)) { @@ -411,7 +443,7 @@ class AlarmRuleState { } private T getPredicateValue(DataSnapshot data, FilterPredicateValue value, AlarmConditionFilter filter, Function transformFunction) { - EntityKeyValue ekv = getDynamicPredicateValue(data, value); + EntityKeyValue ekv = getDynamicPredicateValue(data, value.getDynamicValue()); if (ekv != null) { T result = transformFunction.apply(ekv); if (result != null) { @@ -425,22 +457,22 @@ class AlarmRuleState { } } - private EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue value) { + private EntityKeyValue getDynamicPredicateValue(DataSnapshot data, DynamicValue value) { EntityKeyValue ekv = null; - if (value.getDynamicValue() != null) { - switch (value.getDynamicValue().getSourceType()) { + if (value != null) { + switch (value.getSourceType()) { case CURRENT_DEVICE: - ekv = data.getValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); - if (ekv != null || !value.getDynamicValue().isInherit()) { + ekv = data.getValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getSourceAttribute())); + if (ekv != null || !value.isInherit()) { break; } case CURRENT_CUSTOMER: - ekv = dynamicPredicateValueCtx.getCustomerValue(value.getDynamicValue().getSourceAttribute()); - if (ekv != null || !value.getDynamicValue().isInherit()) { + ekv = dynamicPredicateValueCtx.getCustomerValue(value.getSourceAttribute()); + if (ekv != null || !value.isInherit()) { break; } case CURRENT_TENANT: - ekv = dynamicPredicateValueCtx.getTenantValue(value.getDynamicValue().getSourceAttribute()); + ekv = dynamicPredicateValueCtx.getTenantValue(value.getSourceAttribute()); } } return ekv; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 614cfd4b9c..b0730a5841 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -80,7 +80,7 @@ class AlarmState { public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException { initCurrentAlarm(ctx); - return createOrClearAlarms(ctx, null, ts, null, AlarmRuleState::eval); + return createOrClearAlarms(ctx, null, ts, null, (alarmState, tsParam) -> alarmState.eval(tsParam, dataSnapshot)); } public boolean createOrClearAlarms(TbContext ctx, TbMsg msg, T data, SnapshotUpdate update, BiFunction evalFunction) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java index 8fbacc120d..0cd4ed63ee 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java @@ -19,24 +19,21 @@ import lombok.AccessLevel; import lombok.Getter; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; +import org.thingsboard.server.common.data.device.profile.AlarmConditionSpec; +import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec; +import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValueSourceType; -import org.thingsboard.server.common.data.query.EntityKey; -import org.thingsboard.server.common.data.query.EntityKeyType; -import org.thingsboard.server.common.data.query.FilterPredicateValue; -import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; -import org.thingsboard.server.common.data.query.StringFilterPredicate; -import javax.print.attribute.standard.Severity; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -79,6 +76,7 @@ class ProfileState { ruleKeys.add(keyFilter.getKey()); addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys); } + addEntityKeysFromAlarmConditionSpec(alarmRule); })); if (alarm.getClearRule() != null) { var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>()); @@ -87,11 +85,43 @@ class ProfileState { clearAlarmKeys.add(keyFilter.getKey()); addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys); } + addEntityKeysFromAlarmConditionSpec(alarm.getClearRule()); } } } } + private void addEntityKeysFromAlarmConditionSpec(AlarmRule alarmRule) { + AlarmConditionSpec spec = alarmRule.getCondition().getSpec(); + if (spec == null) { + return; + } + AlarmConditionSpecType specType = spec.getType(); + switch (specType) { + case DURATION: + DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec; + if(duration.getPredicate().getDynamicValue() != null + && duration.getPredicate().getDynamicValue().getSourceAttribute() != null) { + entityKeys.add( + new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, + duration.getPredicate().getDynamicValue().getSourceAttribute()) + ); + } + break; + case REPEATING: + RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec; + if(repeating.getPredicate().getDynamicValue() != null + && repeating.getPredicate().getDynamicValue().getSourceAttribute() != null) { + entityKeys.add( + new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, + repeating.getPredicate().getDynamicValue().getSourceAttribute()) + ); + } + break; + } + + } + private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set entityKeys, Set ruleKeys) { switch (predicate.getType()) { case STRING: diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index faf934cd5e..d3f3280a88 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -42,6 +42,8 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec; +import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -64,12 +66,15 @@ import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.timeseries.TimeseriesService; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.TreeMap; import java.util.UUID; +import java.util.concurrent.TimeUnit; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -94,10 +99,10 @@ public class TbDeviceProfileNodeTest { @Mock private AttributesService attributesService; - private TenantId tenantId = new TenantId(UUID.randomUUID()); - private DeviceId deviceId = new DeviceId(UUID.randomUUID()); - private CustomerId customerId = new CustomerId(UUID.randomUUID()); - private DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID()); + private final TenantId tenantId = new TenantId(UUID.randomUUID()); + private final DeviceId deviceId = new DeviceId(UUID.randomUUID()); + private final CustomerId customerId = new CustomerId(UUID.randomUUID()); + private final DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID()); @Test public void testRandomMessageType() throws Exception { @@ -444,6 +449,642 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } + @Test + public void testCurrentDeviceAttributeForDynamicDurationValue() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + ); + + AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); + alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + long alarmDelayInSeconds = 5L; + alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds); + alarmDelayAttributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData(); + + ListenableFuture> listListenableFuture = + Futures.immediateFuture(Arrays.asList(entry, alarmDelayAttributeKvEntry)); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false) + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + 10L, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", false) + ); + + DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec(); + durationSpec.setUnit(TimeUnit.SECONDS); + durationSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(durationSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFuture); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 35); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds) + .multiply(BigDecimal.valueOf(1000)) + .divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN) + .intValueExact(); + Thread.sleep(halfOfAlarmDelay); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + Thread.sleep(halfOfAlarmDelay); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testInheritTenantAttributeForDuration() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + ); + + AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); + alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + long alarmDelayInSeconds = 5L; + alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds); + alarmDelayAttributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData(); + + ListenableFuture> optionalDurationAttribute = + Futures.immediateFuture(Optional.of(alarmDelayAttributeKvEntry)); + ListenableFuture> listNoDurationAttribute = + Futures.immediateFuture(Collections.singletonList(entry)); + ListenableFuture> emptyOptional = + Futures.immediateFuture(Optional.empty()); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false) + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + 10L, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", true) + ); + + DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec(); + durationSpec.setUnit(TimeUnit.SECONDS); + durationSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(durationSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString())) + .thenReturn(optionalDurationAttribute); + Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) + .thenReturn(device); + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + .thenReturn(emptyOptional); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listNoDurationAttribute); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 150); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds) + .multiply(BigDecimal.valueOf(1000)) + .divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN) + .intValueExact(); + Thread.sleep(halfOfAlarmDelay); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + Thread.sleep(halfOfAlarmDelay); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testCurrentDeviceAttributeForDynamicRepeatingValue() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + ); + + AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); + alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + long alarmRepeating = 2; + alarmDelayAttributeKvEntity.setLongValue(alarmRepeating); + alarmDelayAttributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData(); + + ListenableFuture> listListenableFuture = + Futures.immediateFuture(Arrays.asList(entry, alarmDelayAttributeKvEntry)); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false) + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + 10, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", false) + ); + + + RepeatingAlarmConditionSpec repeatingSpec = new RepeatingAlarmConditionSpec(); + repeatingSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(repeatingSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFuture); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 150); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testInheritTenantAttributeForRepeating() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + ); + + AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); + alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + long repeatingCondition = 2; + alarmDelayAttributeKvEntity.setLongValue(repeatingCondition); + alarmDelayAttributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData(); + + ListenableFuture> optionalDurationAttribute = + Futures.immediateFuture(Optional.of(alarmDelayAttributeKvEntry)); + ListenableFuture> listNoDurationAttribute = + Futures.immediateFuture(Collections.singletonList(entry)); + ListenableFuture> emptyOptional = + Futures.immediateFuture(Optional.empty()); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false) + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + 10, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", true) + ); + + RepeatingAlarmConditionSpec repeatingSpec = new RepeatingAlarmConditionSpec(); + repeatingSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(repeatingSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString())) + .thenReturn(optionalDurationAttribute); + Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) + .thenReturn(device); + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + .thenReturn(emptyOptional); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listNoDurationAttribute); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 150); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testCurrentDeviceAttributeForUseDefaultDurationWhenDynamicDurationValueIsNull() throws Exception { + init(); + + long alarmDelayInSeconds = 5; + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + ListenableFuture> listListenableFuture = + Futures.immediateFuture(Collections.singletonList(entry)); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute") + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + alarmDelayInSeconds, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, null, false) + ); + + DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec(); + durationSpec.setUnit(TimeUnit.SECONDS); + durationSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(durationSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFuture); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 35); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds) + .multiply(BigDecimal.valueOf(1000)) + .divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN) + .intValueExact(); + Thread.sleep(halfOfAlarmDelay); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + Thread.sleep(halfOfAlarmDelay); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testCurrentDeviceAttributeForUseDefaultRepeatingWhenDynamicDurationValueIsNull() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + ListenableFuture> listListenableFuture = + Futures.immediateFuture(Collections.singletonList(entry)); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute") + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + RepeatingAlarmConditionSpec repeating = new RepeatingAlarmConditionSpec(); + repeating.setPredicate(new FilterPredicateValue<>( + 0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_rule", false) + )); + alarmCondition.setSpec(repeating); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFuture); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 35); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test public void testCurrentCustomersAttributeForDynamicValue() throws Exception { init(); diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts index 934a4774fb..998b329dc9 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts @@ -30,7 +30,9 @@ import { DynamicValueSourceType, dynamicValueSourceTypeTranslationMap, EntityKeyValueType, - FilterPredicateValue + FilterPredicateValue, + getDynamicSourcesForAllowUser, + inheritModeForDynamicValueSourceType } from '@shared/models/query/query.models'; @Component({ @@ -52,22 +54,14 @@ import { }) export class FilterPredicateValueComponent implements ControlValueAccessor, Validator, OnInit { - private readonly inheritModeForSources: DynamicValueSourceType[] = [ - DynamicValueSourceType.CURRENT_CUSTOMER, - DynamicValueSourceType.CURRENT_DEVICE]; + private readonly inheritModeForSources: DynamicValueSourceType[] = inheritModeForDynamicValueSourceType; @Input() disabled: boolean; @Input() set allowUserDynamicSource(allow: boolean) { - this.dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_TENANT, - DynamicValueSourceType.CURRENT_CUSTOMER]; + this.dynamicValueSourceTypes = getDynamicSourcesForAllowUser(allow); this.allow = allow; - if (allow) { - this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER); - } else { - this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE); - } } private onlyUserDynamicSourceValue = false; @@ -92,8 +86,9 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali valueTypeEnum = EntityKeyValueType; - dynamicValueSourceTypes: DynamicValueSourceType[] = [DynamicValueSourceType.CURRENT_TENANT, - DynamicValueSourceType.CURRENT_CUSTOMER, DynamicValueSourceType.CURRENT_USER]; + allow = true; + + dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(this.allow); dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; @@ -103,8 +98,6 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali inheritMode = false; - allow = true; - private propagateChange = null; private propagateChangePending = false; diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d0ad42b62e..1ea914f939 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -138,6 +138,7 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb import { EdgeDownlinkTableComponent } from '@home/components/edge/edge-downlink-table.component'; import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-downlink-table-header.component'; import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; +import { AlarmDurationPredicateValueComponent } from '@home/components/profile/alarm/alarm-duration-predicate-value.component'; import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component'; import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component'; @@ -242,6 +243,7 @@ import { SnmpDeviceProfileTransportModule } from '@home/components/profile/devic AlarmScheduleInfoComponent, DeviceProfileProvisionConfigurationComponent, AlarmScheduleComponent, + AlarmDurationPredicateValueComponent, DeviceWizardDialogComponent, DeviceCredentialsComponent, CopyDeviceCredentialsComponent, @@ -358,6 +360,7 @@ import { SnmpDeviceProfileTransportModule } from '@home/components/profile/devic AlarmScheduleInfoComponent, AlarmScheduleComponent, AlarmScheduleDialogComponent, + AlarmDurationPredicateValueComponent, EditAlarmDetailsDialogComponent, DeviceProfileProvisionConfigurationComponent, AlarmScheduleComponent, diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html new file mode 100644 index 0000000000..80841767e0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html @@ -0,0 +1,79 @@ + +
+
+ + + + + {{ defaultValueRequiredError | translate }} + + + {{ defaultValueRangeError | translate }} + + + {{ defaultValueRangeError | translate }} + + + {{ defaultValuePatternError | translate }} + + +
+
+
+
+ + + + + {{'filter.no-dynamic-value' | translate}} + + + {{dynamicValueSourceTypeTranslations.get(sourceType) | translate}} + + + +
+
+ + + + +
+
+ + {{ 'filter.inherit-owner' | translate}} + +
+
+
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss new file mode 100644 index 0000000000..7e140d8e90 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host ::ng-deep { + .source-attribute { + .mat-form-field-infix{ + width: 100%; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts new file mode 100644 index 0000000000..a0a0a0fbab --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts @@ -0,0 +1,153 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + DynamicValueSourceType, + dynamicValueSourceTypeTranslationMap, + FilterPredicateValue, + getDynamicSourcesForAllowUser, + inheritModeForDynamicValueSourceType +} from '@shared/models/query/query.models'; +import { AlarmConditionType } from '@shared/models/device.models'; + +@Component({ + selector: 'tb-alarm-duration-predicate-value', + templateUrl: './alarm-duration-predicate-value.component.html', + styleUrls: ['./alarm-duration-predicate-value.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AlarmDurationPredicateValueComponent), + multi: true + } + ] +}) +export class AlarmDurationPredicateValueComponent implements ControlValueAccessor, OnInit { + + private readonly inheritModeForSources = inheritModeForDynamicValueSourceType; + + @Input() + set alarmConditionType(alarmConditionType: AlarmConditionType) { + switch (alarmConditionType) { + case AlarmConditionType.REPEATING: + this.defaultValuePlaceholder = 'device-profile.condition-repeating-value-required'; + this.defaultValueRequiredError = 'device-profile.condition-repeating-value-range'; + this.defaultValueRangeError = 'device-profile.condition-repeating-value-range'; + this.defaultValuePatternError = 'device-profile.condition-repeating-value-pattern'; + break; + case AlarmConditionType.DURATION: + this.defaultValuePlaceholder = 'device-profile.condition-duration-value'; + this.defaultValueRequiredError = 'device-profile.condition-duration-value-required'; + this.defaultValueRangeError = 'device-profile.condition-duration-value-range'; + this.defaultValuePatternError = 'device-profile.condition-duration-value-pattern'; + break; + } + } + + defaultValuePlaceholder = ''; + defaultValueRequiredError = ''; + defaultValueRangeError = ''; + defaultValuePatternError = ''; + + dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(false); + + dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; + + alarmDurationPredicateValueFormGroup: FormGroup; + + dynamicMode = false; + + inheritMode = false; + + private propagateChange = null; + + constructor(private fb: FormBuilder) { + } + + ngOnInit(): void { + this.alarmDurationPredicateValueFormGroup = this.fb.group({ + defaultValue: [0, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]], + dynamicValue: this.fb.group( + { + sourceType: [null], + sourceAttribute: [null], + inherit: [false] + } + ) + }); + this.alarmDurationPredicateValueFormGroup.get('dynamicValue').get('sourceType').valueChanges.subscribe( + (sourceType) => { + if (!sourceType) { + this.alarmDurationPredicateValueFormGroup.get('dynamicValue').get('sourceAttribute').patchValue(null, {emitEvent: false}); + } + this.updateShowInheritMode(sourceType); + } + ); + this.alarmDurationPredicateValueFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.alarmDurationPredicateValueFormGroup.disable({emitEvent: false}); + } else { + this.alarmDurationPredicateValueFormGroup.enable({emitEvent: false}); + } + } + + writeValue(predicateValue: FilterPredicateValue): void { + this.alarmDurationPredicateValueFormGroup.patchValue({ + defaultValue: predicateValue ? predicateValue.defaultValue : null, + dynamicValue: { + sourceType: predicateValue?.dynamicValue ? predicateValue.dynamicValue.sourceType : null, + sourceAttribute: predicateValue?.dynamicValue ? predicateValue.dynamicValue.sourceAttribute : null, + inherit: predicateValue?.dynamicValue ? predicateValue.dynamicValue.inherit : null + } + }, {emitEvent: false}); + } + + private updateModel() { + let predicateValue: FilterPredicateValue = null; + if (this.alarmDurationPredicateValueFormGroup.valid) { + predicateValue = this.alarmDurationPredicateValueFormGroup.getRawValue(); + if (predicateValue.dynamicValue) { + if (!predicateValue.dynamicValue.sourceType || !predicateValue.dynamicValue.sourceAttribute) { + predicateValue.dynamicValue = null; + } + } + } + this.propagateChange(predicateValue); + } + + private updateShowInheritMode(sourceType: DynamicValueSourceType) { + if (this.inheritModeForSources.includes(sourceType)) { + this.inheritMode = true; + } else { + this.alarmDurationPredicateValueFormGroup.get('dynamicValue.inherit').patchValue(false, {emitEvent: false}); + this.inheritMode = false; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html index 2944d565cd..bc07daddbb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html @@ -37,7 +37,7 @@ [entityId]="entityId" formControlName="keyFilters"> -
+
device-profile.condition-type @@ -49,60 +49,26 @@ {{ 'device-profile.condition-type-required' | translate }} -
- - - - - {{ 'device-profile.condition-duration-value-required' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-pattern' | translate }} - - - - - - - {{ timeUnitTranslations.get(timeUnit) | translate }} - - - - {{ 'device-profile.condition-duration-time-unit-required' | translate }} - - -
-
- - - - - {{ 'device-profile.condition-repeating-value-required' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-pattern' | translate }} - - +
+ + +
+ + + + + {{ timeUnitTranslations.get(timeUnit) | translate }} + + + + {{ 'device-profile.condition-duration-time-unit-required' | translate }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts index a131e9748a..c11d63da2e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts @@ -43,12 +43,11 @@ export interface AlarmRuleConditionDialogData { export class AlarmRuleConditionDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { - timeUnits = Object.keys(TimeUnit); + timeUnits = Object.values(TimeUnit); timeUnitTranslations = timeUnitTranslationMap; - alarmConditionTypes = Object.keys(AlarmConditionType); + alarmConditionTypes = Object.values(AlarmConditionType); AlarmConditionType = AlarmConditionType; alarmConditionTypeTranslation = AlarmConditionTypeTranslationMap; - readonly = this.data.readonly; condition = this.data.condition; entityId = this.data.entityId; @@ -70,9 +69,8 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent; } export interface AlarmCondition { diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index dbe114e18e..e504a3480d 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -202,6 +202,17 @@ export function createDefaultFilterPredicate(valueType: EntityKeyValueType, comp return predicate; } +export function getDynamicSourcesForAllowUser(allow: boolean): DynamicValueSourceType[] { + const dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_TENANT, + DynamicValueSourceType.CURRENT_CUSTOMER]; + if (allow) { + dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER); + } else { + dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE); + } + return dynamicValueSourceTypes; +} + export enum FilterPredicateType { STRING = 'STRING', NUMERIC = 'NUMERIC', @@ -289,6 +300,10 @@ export const dynamicValueSourceTypeTranslationMap = new Map { sourceType: DynamicValueSourceType; sourceAttribute: string; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 8a78b21571..0536cf328f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1179,6 +1179,7 @@ "condition-type-simple": "Simple", "condition-type-duration": "Duration", "condition-during": "During {{during}}", + "condition-during-dynamic": "During \"{{ attribute }}\" ({{during}})", "condition-type-repeating": "Repeating", "condition-type-required": "Condition type is required.", "condition-repeating-value": "Count of events", @@ -1186,6 +1187,7 @@ "condition-repeating-value-pattern": "Count of events should be integers.", "condition-repeating-value-required": "Count of events is required.", "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }", + "condition-repeat-times-dynamic": "Repeats \"{ attribute }\" ({ count, plural, 1 {1 time} other {# times} })", "schedule-type": "Scheduler type", "schedule-type-required": "Scheduler type is required.", "schedule": "Schedule",