From 3351ced60aceb7a4c3e7e5b6010190b99201ae7a Mon Sep 17 00:00:00 2001 From: desoliture Date: Tue, 4 Jan 2022 17:46:46 +0200 Subject: [PATCH 01/14] add backend support for dynamic values for schedules in alarm rules --- .../device/profile/CustomTimeSchedule.java | 3 ++ .../device/profile/SpecificTimeSchedule.java | 3 ++ .../transport/adaptor/JsonConverter.java | 4 ++ .../rule/engine/profile/AlarmRuleState.java | 41 ++++++++++++++----- .../rule/engine/profile/ProfileState.java | 24 +++++++++++ 5 files changed, 64 insertions(+), 11 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java index 89bf54eb25..83e8b3874e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; +import org.thingsboard.server.common.data.query.DynamicValue; import java.util.List; @@ -25,6 +26,8 @@ public class CustomTimeSchedule implements AlarmSchedule { private String timezone; private List items; + private DynamicValue dynamicValue; + @Override public AlarmScheduleType getType() { return AlarmScheduleType.CUSTOM; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java index 9ef17f25b0..57e57a1b24 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; +import org.thingsboard.server.common.data.query.DynamicValue; import java.util.List; import java.util.Set; @@ -28,6 +29,8 @@ public class SpecificTimeSchedule implements AlarmSchedule { private long startsOn; private long endsOn; + private DynamicValue dynamicValue; + @Override public AlarmScheduleType getType() { return AlarmScheduleType.SPECIFIC_TIME; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index be4143e388..af2ac148ce 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -573,6 +573,10 @@ public class JsonConverter { return JSON_PARSER.parse(json); } + public static T parse(String json, Class clazz) { + return fromJson(parse(json), clazz); + } + public static String toJson(JsonElement element) { return GSON.toJson(element); } 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 8ae6390b31..724b10c269 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 @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.StringFilterPredicate; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; import java.time.Instant; import java.time.ZoneId; @@ -115,7 +116,7 @@ class AlarmRuleState { } public AlarmEvalResult eval(DataSnapshot data) { - boolean active = isActive(data.getTs()); + boolean active = isActive(data, data.getTs()); switch (spec.getType()) { case SIMPLE: return (active && eval(alarmRule.getCondition(), data)) ? AlarmEvalResult.TRUE : AlarmEvalResult.FALSE; @@ -128,7 +129,7 @@ class AlarmRuleState { } } - private boolean isActive(long eventTs) { + private boolean isActive(DataSnapshot data, long eventTs) { if (eventTs == 0L) { eventTs = System.currentTimeMillis(); } @@ -138,10 +139,28 @@ class AlarmRuleState { switch (alarmRule.getSchedule().getType()) { case ANY_TIME: return true; - case SPECIFIC_TIME: - return isActiveSpecific((SpecificTimeSchedule) alarmRule.getSchedule(), eventTs); - case CUSTOM: - return isActiveCustom((CustomTimeSchedule) alarmRule.getSchedule(), eventTs); + case SPECIFIC_TIME: { + SpecificTimeSchedule originalSchedule = (SpecificTimeSchedule) alarmRule.getSchedule(); + EntityKeyValue dynamicValue = getDynamicValue(data, originalSchedule.getDynamicValue()); + + if (dynamicValue != null) { + SpecificTimeSchedule schedule = JsonConverter.parse(dynamicValue.getJsonValue(), SpecificTimeSchedule.class); + originalSchedule = schedule == null ? originalSchedule : schedule; + } + + return isActiveSpecific(originalSchedule, eventTs); + } + case CUSTOM: { + CustomTimeSchedule originalSchedule = (CustomTimeSchedule) alarmRule.getSchedule(); + EntityKeyValue dynamicValue = getDynamicValue(data, originalSchedule.getDynamicValue()); + + if (dynamicValue != null) { + CustomTimeSchedule schedule = JsonConverter.parse(dynamicValue.getJsonValue(), CustomTimeSchedule.class); + originalSchedule = schedule == null ? originalSchedule : schedule; + } + + return isActiveCustom(originalSchedule, eventTs); + } default: throw new RuntimeException("Unsupported schedule type: " + alarmRule.getSchedule().getType()); } @@ -236,7 +255,7 @@ class AlarmRuleState { if (repeating.getPredicate().getDynamicValue() != null && repeating.getPredicate().getDynamicValue().getSourceAttribute() != null) { - EntityKeyValue repeatingKeyValue = getDynamicPredicateValue(data, repeating.getPredicate().getDynamicValue()); + EntityKeyValue repeatingKeyValue = getDynamicValue(data, repeating.getPredicate().getDynamicValue()); if (repeatingKeyValue != null) { repeatingTimes = repeatingKeyValue.getLngValue(); } @@ -257,7 +276,7 @@ class AlarmRuleState { if (duration.getPredicate().getDynamicValue() != null && duration.getPredicate().getDynamicValue().getSourceAttribute() != null) { - EntityKeyValue durationKeyValue = getDynamicPredicateValue(data, duration.getPredicate().getDynamicValue()); + EntityKeyValue durationKeyValue = getDynamicValue(data, duration.getPredicate().getDynamicValue()); if (durationKeyValue != null) { durationTimeInMs = timeUnit.toMillis(durationKeyValue.getLngValue()); } @@ -276,7 +295,7 @@ class AlarmRuleState { long requiredDurationInMs = resolveRequiredDurationInMs(dataSnapshot); if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) { long duration = state.getDuration() + (ts - state.getLastEventTs()); - if (isActive(ts)) { + if (isActive(dataSnapshot, ts)) { return duration > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; } else { return AlarmEvalResult.FALSE; @@ -443,7 +462,7 @@ class AlarmRuleState { } private T getPredicateValue(DataSnapshot data, FilterPredicateValue value, AlarmConditionFilter filter, Function transformFunction) { - EntityKeyValue ekv = getDynamicPredicateValue(data, value.getDynamicValue()); + EntityKeyValue ekv = getDynamicValue(data, value.getDynamicValue()); if (ekv != null) { T result = transformFunction.apply(ekv); if (result != null) { @@ -457,7 +476,7 @@ class AlarmRuleState { } } - private EntityKeyValue getDynamicPredicateValue(DataSnapshot data, DynamicValue value) { + private EntityKeyValue getDynamicValue(DataSnapshot data, DynamicValue value) { EntityKeyValue ekv = null; if (value != null) { switch (value.getSourceType()) { 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 0cd4ed63ee..6d3c48892c 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 @@ -27,6 +27,9 @@ 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.device.profile.CustomTimeSchedule; +import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; +import org.thingsboard.server.common.data.device.profile.AlarmSchedule; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.DynamicValue; @@ -77,6 +80,7 @@ class ProfileState { addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys); } addEntityKeysFromAlarmConditionSpec(alarmRule); + addScheduleDynamicValues(alarmRule.getSchedule()); })); if (alarm.getClearRule() != null) { var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>()); @@ -91,6 +95,26 @@ class ProfileState { } } + private void addScheduleDynamicValues(AlarmSchedule schedule) { + DynamicValue dynamicValue = null; + switch (schedule.getType()) { + case SPECIFIC_TIME: + SpecificTimeSchedule specSchedule = (SpecificTimeSchedule) schedule; + dynamicValue = specSchedule.getDynamicValue(); + break; + case CUSTOM: + CustomTimeSchedule custSchedule = (CustomTimeSchedule) schedule; + dynamicValue = custSchedule.getDynamicValue(); + } + + if (dynamicValue != null) { + entityKeys.add( + new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, + dynamicValue.getSourceAttribute()) + ); + } + } + private void addEntityKeysFromAlarmConditionSpec(AlarmRule alarmRule) { AlarmConditionSpec spec = alarmRule.getCondition().getSpec(); if (spec == null) { From 2b203bfdd59cc7b2455411bc6230c2fdec9c5689 Mon Sep 17 00:00:00 2001 From: desoliture Date: Wed, 5 Jan 2022 13:24:53 +0200 Subject: [PATCH 02/14] fix notNull checking for schedule in ProfileState, rename variables and fix logic for parsing dynamicValues in AlarmRuleState --- .../rule/engine/profile/AlarmRuleState.java | 28 +++++++++++-------- .../rule/engine/profile/ProfileState.java | 14 ++++++---- 2 files changed, 25 insertions(+), 17 deletions(-) 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 724b10c269..c5ad4607b2 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 @@ -140,26 +140,30 @@ class AlarmRuleState { case ANY_TIME: return true; case SPECIFIC_TIME: { - SpecificTimeSchedule originalSchedule = (SpecificTimeSchedule) alarmRule.getSchedule(); - EntityKeyValue dynamicValue = getDynamicValue(data, originalSchedule.getDynamicValue()); + SpecificTimeSchedule defaultSchedule = (SpecificTimeSchedule) alarmRule.getSchedule(); + EntityKeyValue dynamicValue = getDynamicValue(data, defaultSchedule.getDynamicValue()); - if (dynamicValue != null) { - SpecificTimeSchedule schedule = JsonConverter.parse(dynamicValue.getJsonValue(), SpecificTimeSchedule.class); - originalSchedule = schedule == null ? originalSchedule : schedule; + SpecificTimeSchedule schedule; + try { + schedule = JsonConverter.parse(dynamicValue.getJsonValue(), SpecificTimeSchedule.class); + } catch (Exception e) { + schedule = defaultSchedule; } - return isActiveSpecific(originalSchedule, eventTs); + return isActiveSpecific(schedule, eventTs); } case CUSTOM: { - CustomTimeSchedule originalSchedule = (CustomTimeSchedule) alarmRule.getSchedule(); - EntityKeyValue dynamicValue = getDynamicValue(data, originalSchedule.getDynamicValue()); + CustomTimeSchedule defaultSchedule = (CustomTimeSchedule) alarmRule.getSchedule(); + EntityKeyValue dynamicValue = getDynamicValue(data, defaultSchedule.getDynamicValue()); - if (dynamicValue != null) { - CustomTimeSchedule schedule = JsonConverter.parse(dynamicValue.getJsonValue(), CustomTimeSchedule.class); - originalSchedule = schedule == null ? originalSchedule : schedule; + CustomTimeSchedule schedule; + try { + schedule = JsonConverter.parse(dynamicValue.getJsonValue(), CustomTimeSchedule.class); + } catch (Exception e) { + schedule = defaultSchedule; } - return isActiveCustom(originalSchedule, eventTs); + return isActiveCustom(schedule, eventTs); } default: throw new RuntimeException("Unsupported schedule type: " + alarmRule.getSchedule().getType()); 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 6d3c48892c..c8751d65f7 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 @@ -80,7 +80,10 @@ class ProfileState { addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys); } addEntityKeysFromAlarmConditionSpec(alarmRule); - addScheduleDynamicValues(alarmRule.getSchedule()); + AlarmSchedule schedule = alarmRule.getSchedule(); + if (schedule != null) { + addScheduleDynamicValues(schedule); + } })); if (alarm.getClearRule() != null) { var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>()); @@ -99,12 +102,13 @@ class ProfileState { DynamicValue dynamicValue = null; switch (schedule.getType()) { case SPECIFIC_TIME: - SpecificTimeSchedule specSchedule = (SpecificTimeSchedule) schedule; - dynamicValue = specSchedule.getDynamicValue(); + SpecificTimeSchedule specificTimeSchedule = (SpecificTimeSchedule) schedule; + dynamicValue = specificTimeSchedule.getDynamicValue(); break; case CUSTOM: - CustomTimeSchedule custSchedule = (CustomTimeSchedule) schedule; - dynamicValue = custSchedule.getDynamicValue(); + CustomTimeSchedule customTimeSchedule = (CustomTimeSchedule) schedule; + dynamicValue = customTimeSchedule.getDynamicValue(); + break; } if (dynamicValue != null) { From 46e301b2dbc5bc9aaa6e8850961ee5fad1f08093 Mon Sep 17 00:00:00 2001 From: desoliture Date: Wed, 5 Jan 2022 18:14:37 +0200 Subject: [PATCH 03/14] Refactoring for alarm rule schedule add getDynamicValue method to AlarmSchedule interface, refactor main isActive processing methods for work with schedules and dynamic values --- .../data/device/profile/AlarmSchedule.java | 3 ++ .../data/device/profile/AnyTimeSchedule.java | 7 +++ .../device/profile/SpecificTimeSchedule.java | 1 - .../rule/engine/profile/AlarmRuleState.java | 49 ++++++++----------- .../rule/engine/profile/ProfileState.java | 15 +----- 5 files changed, 32 insertions(+), 43 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java index 1d5eceb1aa..1fc39ef147 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.query.DynamicValue; import java.io.Serializable; @@ -34,4 +35,6 @@ public interface AlarmSchedule extends Serializable { AlarmScheduleType getType(); + DynamicValue getDynamicValue(); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java index e02086902f..94eea60950 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.common.data.device.profile; +import org.thingsboard.server.common.data.query.DynamicValue; + public class AnyTimeSchedule implements AlarmSchedule { @Override @@ -22,4 +24,9 @@ public class AnyTimeSchedule implements AlarmSchedule { return AlarmScheduleType.ANY_TIME; } + @Override + public DynamicValue getDynamicValue() { + return null; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java index 57e57a1b24..0b8dfde791 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java @@ -18,7 +18,6 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; import org.thingsboard.server.common.data.query.DynamicValue; -import java.util.List; import java.util.Set; @Data 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 c5ad4607b2..abcc414206 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 @@ -27,6 +27,7 @@ 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.AlarmSchedule; import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem; import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec; import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec; @@ -139,37 +140,29 @@ class AlarmRuleState { switch (alarmRule.getSchedule().getType()) { case ANY_TIME: return true; - case SPECIFIC_TIME: { - SpecificTimeSchedule defaultSchedule = (SpecificTimeSchedule) alarmRule.getSchedule(); - EntityKeyValue dynamicValue = getDynamicValue(data, defaultSchedule.getDynamicValue()); - - SpecificTimeSchedule schedule; - try { - schedule = JsonConverter.parse(dynamicValue.getJsonValue(), SpecificTimeSchedule.class); - } catch (Exception e) { - schedule = defaultSchedule; - } - - return isActiveSpecific(schedule, eventTs); - } - case CUSTOM: { - CustomTimeSchedule defaultSchedule = (CustomTimeSchedule) alarmRule.getSchedule(); - EntityKeyValue dynamicValue = getDynamicValue(data, defaultSchedule.getDynamicValue()); - - CustomTimeSchedule schedule; - try { - schedule = JsonConverter.parse(dynamicValue.getJsonValue(), CustomTimeSchedule.class); - } catch (Exception e) { - schedule = defaultSchedule; - } - - return isActiveCustom(schedule, eventTs); - } + case SPECIFIC_TIME: + return isActiveSpecific((SpecificTimeSchedule) getSchedule(data, alarmRule), eventTs); + case CUSTOM: + return isActiveCustom((CustomTimeSchedule) getSchedule(data, alarmRule), eventTs); default: throw new RuntimeException("Unsupported schedule type: " + alarmRule.getSchedule().getType()); } } + private AlarmSchedule getSchedule(DataSnapshot data, AlarmRule alarmRule) { + AlarmSchedule schedule = alarmRule.getSchedule(); + EntityKeyValue dynamicValue = getDynamicValue(data, schedule.getDynamicValue()); + + if (dynamicValue != null) { + try { + return JsonConverter.parse(dynamicValue.getJsonValue(), alarmRule.getSchedule().getClass()); + } catch (Exception e) { + log.trace("Failed to parse AlarmSchedule from dynamicValue: {}", dynamicValue.getJsonValue(), e); + } + } + return schedule; + } + private boolean isActiveSpecific(SpecificTimeSchedule schedule, long eventTs) { ZoneId zoneId = SchedulerUtils.getZoneId(schedule.getTimezone()); ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventTs), zoneId); @@ -252,7 +245,7 @@ class AlarmRuleState { long repeatingTimes = 0; AlarmConditionSpec alarmConditionSpec = getSpec(); AlarmConditionSpecType specType = alarmConditionSpec.getType(); - if(specType.equals(AlarmConditionSpecType.REPEATING)) { + if (specType.equals(AlarmConditionSpecType.REPEATING)) { RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec; repeatingTimes = repeating.getPredicate().getDefaultValue(); @@ -272,7 +265,7 @@ class AlarmRuleState { long durationTimeInMs = 0; AlarmConditionSpec alarmConditionSpec = getSpec(); AlarmConditionSpecType specType = alarmConditionSpec.getType(); - if(specType.equals(AlarmConditionSpecType.DURATION)) { + if (specType.equals(AlarmConditionSpecType.DURATION)) { DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec; TimeUnit timeUnit = duration.getUnit(); 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 c8751d65f7..dcfcea98ba 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 @@ -27,8 +27,6 @@ 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.device.profile.CustomTimeSchedule; -import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; import org.thingsboard.server.common.data.device.profile.AlarmSchedule; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; @@ -99,18 +97,7 @@ class ProfileState { } private void addScheduleDynamicValues(AlarmSchedule schedule) { - DynamicValue dynamicValue = null; - switch (schedule.getType()) { - case SPECIFIC_TIME: - SpecificTimeSchedule specificTimeSchedule = (SpecificTimeSchedule) schedule; - dynamicValue = specificTimeSchedule.getDynamicValue(); - break; - case CUSTOM: - CustomTimeSchedule customTimeSchedule = (CustomTimeSchedule) schedule; - dynamicValue = customTimeSchedule.getDynamicValue(); - break; - } - + DynamicValue dynamicValue = schedule.getDynamicValue(); if (dynamicValue != null) { entityKeys.add( new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, From 03256deee78c9b30d2b13a646c5c53db9a03bfbc Mon Sep 17 00:00:00 2001 From: desoliture Date: Thu, 6 Jan 2022 14:05:12 +0200 Subject: [PATCH 04/14] add corresponding tests for schedule from dynamic values --- .../profile/TbDeviceProfileNodeTest.java | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) 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 b2c79102c4..63dc9a81d2 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 @@ -44,6 +44,8 @@ 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.device.profile.CustomTimeSchedule; +import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -71,6 +73,7 @@ import java.math.RoundingMode; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.ArrayList; import java.util.Optional; import java.util.TreeMap; import java.util.UUID; @@ -1086,6 +1089,182 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } + @Test + public void testActiveAlarmScheduleFromDynamicValuesWhenDefaultScheduleIsInactive() 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 compositeKeyActiveSchedule = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "dynamicValueActiveSchedule" + ); + + AttributeKvEntity attributeKvEntityActiveSchedule = new AttributeKvEntity(); + attributeKvEntityActiveSchedule.setId(compositeKeyActiveSchedule); + attributeKvEntityActiveSchedule.setJsonValue( + "{\"timezone\":\"Europe/Kiev\",\"items\":[{\"enabled\":true,\"dayOfWeek\":1,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":2,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":3,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":4,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":5,\"startsOn\":0,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":6,\"startsOn\":8.64e+7,\"endsOn\":8.64e+7},{\"enabled\":true,\"dayOfWeek\":7,\"startsOn\":0,\"endsOn\":8.64e+7}],\"dynamicValue\":null}" + ); + attributeKvEntityActiveSchedule.setLastUpdateTs(0L); + + AttributeKvEntry entryActiveSchedule = attributeKvEntityActiveSchedule.toData(); + + ListenableFuture> listListenableFutureActiveSchedule = + Futures.immediateFuture(Collections.singletonList(entryActiveSchedule)); + + 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, + null + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + CustomTimeSchedule schedule = new CustomTimeSchedule(); + schedule.setItems(Collections.emptyList()); + schedule.setDynamicValue(new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "dynamicValueActiveSchedule", false)); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + alarmRule.setSchedule(schedule); + DeviceProfileAlarm deviceProfileAlarmActiveSchedule = new DeviceProfileAlarm(); + deviceProfileAlarmActiveSchedule.setId("highTemperatureAlarmID"); + deviceProfileAlarmActiveSchedule.setAlarmType("highTemperatureAlarm"); + deviceProfileAlarmActiveSchedule.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(deviceProfileAlarmActiveSchedule)); + 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(listListenableFutureActiveSchedule); + + 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).enqueueForTellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testInactiveAlarmScheduleFromDynamicValuesWhenDefaultScheduleIsActive() 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 compositeKeyInactiveSchedule = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "dynamicValueInactiveSchedule" + ); + + AttributeKvEntity attributeKvEntityInactiveSchedule = new AttributeKvEntity(); + attributeKvEntityInactiveSchedule.setId(compositeKeyInactiveSchedule); + attributeKvEntityInactiveSchedule.setJsonValue( + "{\"timezone\":\"Europe/Kiev\",\"items\":[{\"enabled\":false,\"dayOfWeek\":1,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":2,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":3,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":4,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":5,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":6,\"startsOn\":0,\"endsOn\":0},{\"enabled\":false,\"dayOfWeek\":7,\"startsOn\":0,\"endsOn\":0}],\"dynamicValue\":null}" + ); + + attributeKvEntityInactiveSchedule.setLastUpdateTs(0L); + + AttributeKvEntry entryInactiveSchedule = attributeKvEntityInactiveSchedule.toData(); + + ListenableFuture> listListenableFutureInactiveSchedule = + Futures.immediateFuture(Collections.singletonList(entryInactiveSchedule)); + + 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, + null + )); + + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + CustomTimeSchedule schedule = new CustomTimeSchedule(); + + List items = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + CustomTimeScheduleItem item = new CustomTimeScheduleItem(); + item.setEnabled(true); + item.setDayOfWeek(i + 1); + item.setEndsOn(0); + item.setStartsOn(0); + items.add(item); + } + + schedule.setItems(items); + schedule.setDynamicValue(new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "dynamicValueInactiveSchedule", false)); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + alarmRule.setSchedule(schedule); + DeviceProfileAlarm deviceProfileAlarmNonactiveSchedule = new DeviceProfileAlarm(); + deviceProfileAlarmNonactiveSchedule.setId("highTemperatureAlarmID"); + deviceProfileAlarmNonactiveSchedule.setAlarmType("highTemperatureAlarm"); + deviceProfileAlarmNonactiveSchedule.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(deviceProfileAlarmNonactiveSchedule)); + 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(listListenableFutureInactiveSchedule); + + 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, Mockito.never()).enqueueForTellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } @Test public void testCurrentCustomersAttributeForDynamicValue() throws Exception { From 8f485010f0b0a4328c29cb23f57c37c1a705e3ee Mon Sep 17 00:00:00 2001 From: desoliture Date: Thu, 6 Jan 2022 16:14:46 +0200 Subject: [PATCH 05/14] refactoring --- .../rule/engine/profile/TbDeviceProfileNodeTest.java | 3 --- 1 file changed, 3 deletions(-) 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 63dc9a81d2..30e2943110 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 @@ -1246,14 +1246,11 @@ public class TbDeviceProfileNodeTest { .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(listListenableFutureInactiveSchedule); 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); From 6875df9dab6750d2ba0fe2b55c844e639fbfc343 Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Mon, 10 Jan 2022 16:49:15 +0200 Subject: [PATCH 06/14] Added Dynamic value to alarm schedule --- .../alarm/alarm-schedule.component.html | 40 ++++++++++++++++++- .../profile/alarm/alarm-schedule.component.ts | 28 +++++++++++-- ui-ngx/src/app/shared/models/device.models.ts | 4 ++ .../assets/locale/locale.constant-en_US.json | 1 + 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html index bf09b33d76..5d63b48f15 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html @@ -73,8 +73,44 @@
-
device-profile.schedule-days
- + + +
+ +
+ {{'filter.dynamic-value' | translate}} +
+
+ +
+
+ +
+
+
+ + + + + {{'filter.no-dynamic-value' | translate}} + + + {{dynamicValueSourceTypeTranslations.get(sourceType) | translate}} + + + +
+
+ + + + +
+
+
+
+
+
device-profile.schedule-days
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts index d5c9f02876..6fd679f3f7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts @@ -40,6 +40,12 @@ import { import { isDefined, isDefinedAndNotNull } from '@core/utils'; import { MatCheckboxChange } from '@angular/material/checkbox'; import { getDefaultTimezone } from '@shared/models/time/time.models'; +import { + DynamicValueSourceType, + dynamicValueSourceTypeTranslationMap, + getDynamicSourcesForAllowUser +} from '@shared/models/query/query.models'; +import { emit } from 'cluster'; @Component({ selector: 'tb-alarm-schedule', @@ -64,7 +70,8 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, alarmScheduleTypes = Object.keys(AlarmScheduleType); alarmScheduleType = AlarmScheduleType; alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap; - + dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(false); + dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; dayOfWeekTranslationsArray = dayOfWeekTranslations; allDays = Array(7).fill(0).map((x, i) => i); @@ -91,8 +98,19 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, daysOfWeek: this.fb.array(new Array(7).fill(false), this.validateDayOfWeeks), startsOn: [0, Validators.required], endsOn: [0, Validators.required], - items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems) + items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems), + dynamicValue: this.fb.group({ + sourceType: [null], + sourceAttribute: [null] + }) }); + + this.alarmScheduleForm.get('dynamicValue.sourceType').valueChanges.subscribe((sourceType) => { + if (!sourceType) { + this.alarmScheduleForm.get('dynamicValue.sourceAttribute').patchValue('', {emitEvent:false}); + } + }) + this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { const defaultTimezone = getDefaultTimezone(); this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false}); @@ -177,7 +195,11 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, this.alarmScheduleForm.patchValue({ type: this.modelValue.type, timezone: this.modelValue.timezone, - items: alarmDays + items: alarmDays, + dynamicValue: { + sourceAttribute: this.modelValue.dynamicValue.sourceAttribute, + sourceType: this.modelValue.dynamicValue.sourceType + } }, {emitEvent: false}); } break; diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 7fc3d70f78..ffb944803c 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -477,6 +477,10 @@ export const AlarmScheduleTypeTranslationMap = new Map Date: Tue, 11 Jan 2022 14:38:07 +0200 Subject: [PATCH 07/14] Updated dynamic value logic --- .../home/components/home-components.module.ts | 4 +- .../alarm/alarm-dynamic-value.component.html | 37 ++++++++++ .../alarm/alarm-dynamic-value.component.ts | 68 +++++++++++++++++++ .../alarm/alarm-schedule.component.html | 41 +---------- .../profile/alarm/alarm-schedule.component.ts | 27 ++------ 5 files changed, 115 insertions(+), 62 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts 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 73361eddce..6614edebe8 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 @@ -147,6 +147,7 @@ import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; import { DashboardStateComponent } from '@home/components/dashboard-page/dashboard-state.component'; +import { AlarmDynamicValue } from '@home/components/profile/alarm/alarm-dynamic-value.component'; @NgModule({ declarations: @@ -245,6 +246,7 @@ import { DashboardStateComponent } from '@home/components/dashboard-page/dashboa AlarmScheduleInfoComponent, DeviceProfileProvisionConfigurationComponent, AlarmScheduleComponent, + AlarmDynamicValue, AlarmDurationPredicateValueComponent, DeviceWizardDialogComponent, AlarmScheduleDialogComponent, @@ -356,11 +358,11 @@ import { DashboardStateComponent } from '@home/components/dashboard-page/dashboa DeviceWizardDialogComponent, AlarmScheduleInfoComponent, AlarmScheduleComponent, + AlarmDynamicValue, AlarmScheduleDialogComponent, AlarmDurationPredicateValueComponent, EditAlarmDetailsDialogComponent, DeviceProfileProvisionConfigurationComponent, - AlarmScheduleComponent, SmsProviderConfigurationComponent, AwsSnsProviderConfigurationComponent, TwilioSmsProviderConfigurationComponent, diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html new file mode 100644 index 0000000000..6b2852cd68 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html @@ -0,0 +1,37 @@ + + +
+ +
+ {{'filter.dynamic-value' | translate}} +
+
+ +
+
+ +
+
+
+ + + + + {{'filter.no-dynamic-value' | translate}} + + + {{dynamicValueSourceTypeTranslations.get(sourceType) | translate}} + + + +
+
+ + + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts new file mode 100644 index 0000000000..cb4efc1480 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts @@ -0,0 +1,68 @@ +import { Component, forwardRef, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALUE_ACCESSOR, +} from '@angular/forms'; +import { + DynamicValueSourceType, + dynamicValueSourceTypeTranslationMap, + getDynamicSourcesForAllowUser +} from '@shared/models/query/query.models'; + +@Component({ + selector: 'tb-alarm-dynamic-value', + templateUrl: './alarm-dynamic-value.component.html', + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AlarmDynamicValue), + multi: true + }] +}) + +export class AlarmDynamicValue implements ControlValueAccessor, OnInit{ + public dynamicValue: FormGroup; + public dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(false); + public dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder) { + } + + ngOnInit(): void { + this.dynamicValue = this.fb.group({ + sourceType: [null], + sourceAttribute: [null] + }) + + this.dynamicValue.get('sourceType').valueChanges.subscribe( + (sourceType) => { + if (!sourceType) { + this.dynamicValue.get('sourceAttribute').patchValue(null, {emitEvent: false}); + } + } + ); + + this.dynamicValue.valueChanges.subscribe(() => { + this.updateModel(); + }) + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + writeValue(dynamicValue: {sourceType: string, sourceAttribute: string}): void { + if(dynamicValue) { + this.dynamicValue.patchValue(dynamicValue, {emitEvent: false}); + } + } + + private updateModel() { + this.propagateChange(this.dynamicValue.value); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html index 5d63b48f15..67a3ac69c0 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html @@ -34,6 +34,7 @@ formControlName="timezone">
+
device-profile.schedule-days
@@ -73,44 +74,8 @@
- - -
- -
- {{'filter.dynamic-value' | translate}} -
-
- -
-
- -
-
-
- - - - - {{'filter.no-dynamic-value' | translate}} - - - {{dynamicValueSourceTypeTranslations.get(sourceType) | translate}} - - - -
-
- - - - -
-
-
-
-
-
device-profile.schedule-days
+ +
device-profile.schedule-days
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts index 6fd679f3f7..2deef61a6d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.ts @@ -40,12 +40,6 @@ import { import { isDefined, isDefinedAndNotNull } from '@core/utils'; import { MatCheckboxChange } from '@angular/material/checkbox'; import { getDefaultTimezone } from '@shared/models/time/time.models'; -import { - DynamicValueSourceType, - dynamicValueSourceTypeTranslationMap, - getDynamicSourcesForAllowUser -} from '@shared/models/query/query.models'; -import { emit } from 'cluster'; @Component({ selector: 'tb-alarm-schedule', @@ -70,8 +64,6 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, alarmScheduleTypes = Object.keys(AlarmScheduleType); alarmScheduleType = AlarmScheduleType; alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap; - dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(false); - dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; dayOfWeekTranslationsArray = dayOfWeekTranslations; allDays = Array(7).fill(0).map((x, i) => i); @@ -99,18 +91,9 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, startsOn: [0, Validators.required], endsOn: [0, Validators.required], items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems), - dynamicValue: this.fb.group({ - sourceType: [null], - sourceAttribute: [null] - }) + dynamicValue: [null] }); - this.alarmScheduleForm.get('dynamicValue.sourceType').valueChanges.subscribe((sourceType) => { - if (!sourceType) { - this.alarmScheduleForm.get('dynamicValue.sourceAttribute').patchValue('', {emitEvent:false}); - } - }) - this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { const defaultTimezone = getDefaultTimezone(); this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false}); @@ -176,7 +159,8 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, timezone: this.modelValue.timezone, daysOfWeek, startsOn: utcTimestampToTimeOfDay(this.modelValue.startsOn), - endsOn: utcTimestampToTimeOfDay(this.modelValue.endsOn) + endsOn: utcTimestampToTimeOfDay(this.modelValue.endsOn), + dynamicValue: this.modelValue.dynamicValue }, {emitEvent: false}); break; case AlarmScheduleType.CUSTOM: @@ -196,10 +180,7 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, type: this.modelValue.type, timezone: this.modelValue.timezone, items: alarmDays, - dynamicValue: { - sourceAttribute: this.modelValue.dynamicValue.sourceAttribute, - sourceType: this.modelValue.dynamicValue.sourceType - } + dynamicValue: this.modelValue.dynamicValue }, {emitEvent: false}); } break; From 877257202e9e96925d684e8d448259d0ceada56b Mon Sep 17 00:00:00 2001 From: desoliture Date: Tue, 11 Jan 2022 15:38:20 +0200 Subject: [PATCH 08/14] fix incorrect work if endsOn equals zero when choosing schedule to be all day active (from 00:00 to 00:00) startsOn and endsOn equals zero, in this case make endsOn equals 24hours in millis --- .../thingsboard/rule/engine/profile/AlarmRuleState.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 abcc414206..f997efdeb3 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 @@ -182,7 +182,12 @@ class AlarmRuleState { for (CustomTimeScheduleItem item : schedule.getItems()) { if (item.getDayOfWeek() == dayOfWeek) { if (item.isEnabled()) { - return isActive(eventTs, zoneId, zdt, item.getStartsOn(), item.getEndsOn()); + long endsOn = item.getEndsOn(); + if (endsOn == 0) { + // 24 hours in milliseconds + endsOn = 86400000; + } + return isActive(eventTs, zoneId, zdt, item.getStartsOn(), endsOn); } else { return false; } From 7328012cb491b16eebb89f1c6c0380861bf94c7f Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Tue, 11 Jan 2022 16:06:08 +0200 Subject: [PATCH 09/14] Added license headers --- .../alarm/alarm-dynamic-value.component.html | 17 +++++++++++++++++ .../alarm/alarm-dynamic-value.component.ts | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html index 6b2852cd68..50b2dee7a7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html @@ -1,3 +1,20 @@ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts index cb4efc1480..ad4b8cc02c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts @@ -1,3 +1,19 @@ +/// +/// 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, OnInit } from '@angular/core'; import { ControlValueAccessor, From 6267430dd989afaf3c2a34704456db37199e7c90 Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Mon, 24 Jan 2022 11:34:37 +0200 Subject: [PATCH 10/14] Add ui-help --- .../profile/alarm/alarm-dynamic-value.component.html | 1 + .../profile/alarm/alarm-dynamic-value.component.ts | 7 +++++-- .../components/profile/alarm/alarm-schedule.component.html | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html index 50b2dee7a7..99eee091cb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html @@ -48,6 +48,7 @@
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts index ad4b8cc02c..975331b7b4 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts @@ -14,8 +14,9 @@ /// limitations under the License. /// -import { Component, forwardRef, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { + AbstractControl, ControlValueAccessor, FormBuilder, FormGroup, @@ -43,12 +44,14 @@ export class AlarmDynamicValue implements ControlValueAccessor, OnInit{ public dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; private propagateChange = (v: any) => { }; + @Input() helpId: string; + constructor(private fb: FormBuilder) { } ngOnInit(): void { this.dynamicValue = this.fb.group({ - sourceType: [null], + sourceType: [null, []], sourceAttribute: [null] }) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html index 67a3ac69c0..7cb5a3029e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-schedule.component.html @@ -34,7 +34,7 @@ formControlName="timezone">
- +
device-profile.schedule-days
@@ -74,7 +74,7 @@
- +
device-profile.schedule-days
From 5118b21f2662a8c8899ec9ec2d71f433e57805c8 Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Fri, 28 Jan 2022 17:10:43 +0200 Subject: [PATCH 11/14] Added UI help for dynamic value in alarm schedule --- .../alarm_specific_schedule_format.md | 31 ++++++++ .../alarm_сustom_schedule_format.md | 79 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 ui-ngx/src/assets/help/en_US/device-profile/alarm_specific_schedule_format.md create mode 100644 ui-ngx/src/assets/help/en_US/device-profile/alarm_сustom_schedule_format.md diff --git a/ui-ngx/src/assets/help/en_US/device-profile/alarm_specific_schedule_format.md b/ui-ngx/src/assets/help/en_US/device-profile/alarm_specific_schedule_format.md new file mode 100644 index 0000000000..cbf6b3a3ea --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/device-profile/alarm_specific_schedule_format.md @@ -0,0 +1,31 @@ +#### Specific schedule format + +An attribute with a dynamic value for a specific schedule format must have JSON in the following format: + +```javascript +{ + "daysOfWeek": [ + 2, + 4 + ], + "endsOn": 0, + "startsOn": 0, + "timezone": "Europe/Kiev" +} +``` + +
    +
  • +timezone: this value is used to designate the timezone you are using. +
  • +
  • +daysOfWeek: this value is used to designate the days in numerical representation (Monday - 1, Tuesday 2, etc.) on which the schedule will be active. +
  • +
  • +startsOn: this value is used to designate the timestamp in milliseconds, from which the schedule will be active for the designated days. +
  • +
  • +endsOn: this value is used to designate the timestamp in milliseconds until which the schedule will be active for the specified days. +
  • +
+When startsOn and endsOn equals 0 it's means that the schedule will be active the whole day. diff --git a/ui-ngx/src/assets/help/en_US/device-profile/alarm_сustom_schedule_format.md b/ui-ngx/src/assets/help/en_US/device-profile/alarm_сustom_schedule_format.md new file mode 100644 index 0000000000..7090ae68e1 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/device-profile/alarm_сustom_schedule_format.md @@ -0,0 +1,79 @@ +#### Custom schedule format + +An attribute with a dynamic value for a custom schedule format must have JSON in the following format: + +```javascript +{ + "timezone": "Europe/Kiev", + "items": [ + { + "dayOfWeek": 1, + "enabled": true, + "endsOn": 0, + "startsOn": 0 + }, + { + "dayOfWeek": 2, + "enabled": true, + "endsOn": 0, + "startsOn": 0 + }, + { + "dayOfWeek": 3, + "enabled": true, + "endsOn": 0, + "startsOn": 0 + }, + { + "dayOfWeek": 4, + "enabled": true, + "endsOn": 0, + "startsOn": 0 + }, + { + "dayOfWeek": 5, + "enabled": true, + "endsOn": 0, + "startsOn": 0 + }, + { + "dayOfWeek": 6, + "enabled": true, + "endsOn": 0, + "startsOn": 0 + }, + { + "dayOfWeek": 7, + "enabled": true, + "endsOn": 0, + "startsOn": 0 + } + ] +} +``` + +
    +
  • +timezone: this value is used to designate the timezone you are using. +
  • +
  • +items: the array of values representing the days on which the schedule will be active. +
  • +
+ +One array item contains such fields: +
    +
  • +dayOfWeek: this value is used to designate the specified day in numerical representation (Monday - 1, Tuesday 2, etc.) on which the schedule will be active. +
  • +
  • +enabled: this boolean value, used to designate that the specified day in the schedule will be enabled. +
  • +
  • +startsOn: this value is used to designate the timestamp in milliseconds, from which the schedule will be active for the designated day. +
  • +
  • +endsOn: this value is used to designate the timestamp in milliseconds until which the schedule will be active for the specified day. +
  • +
+When startsOn and endsOn equals 0 it's means that the schedule will be active the whole day. From 440e5eb67bbfcda352cf9cd943e35e29bc748bbc Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Tue, 1 Feb 2022 18:09:08 +0200 Subject: [PATCH 12/14] Added disable mod --- .../alarm/alarm-dynamic-value.component.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts index 975331b7b4..910aa99962 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.ts @@ -16,7 +16,6 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { - AbstractControl, ControlValueAccessor, FormBuilder, FormGroup, @@ -44,7 +43,11 @@ export class AlarmDynamicValue implements ControlValueAccessor, OnInit{ public dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; private propagateChange = (v: any) => { }; - @Input() helpId: string; + @Input() + helpId: string; + + @Input() + disabled: boolean; constructor(private fb: FormBuilder) { } @@ -81,6 +84,15 @@ export class AlarmDynamicValue implements ControlValueAccessor, OnInit{ } } + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.dynamicValue.disable({emitEvent: false}); + } else { + this.dynamicValue.enable({emitEvent: false}); + } + } + private updateModel() { this.propagateChange(this.dynamicValue.value); } From ae85dae8bd8a7c89468155979f43056302655dea Mon Sep 17 00:00:00 2001 From: desoliture Date: Tue, 1 Feb 2022 18:23:06 +0200 Subject: [PATCH 13/14] fix processing isActive logic in custom schedules when endsOn equals zero --- .../thingsboard/rule/engine/profile/AlarmRuleState.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 f997efdeb3..ec7f38576e 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 @@ -172,7 +172,13 @@ class AlarmRuleState { return false; } } - return isActive(eventTs, zoneId, zdt, schedule.getStartsOn(), schedule.getEndsOn()); + long endsOn = schedule.getEndsOn(); + if (endsOn == 0) { + // 24 hours in milliseconds + endsOn = 86400000; + } + + return isActive(eventTs, zoneId, zdt, schedule.getStartsOn(), endsOn); } private boolean isActiveCustom(CustomTimeSchedule schedule, long eventTs) { From 09d898c3a70a224b38c7d3e7ab89084426c17113 Mon Sep 17 00:00:00 2001 From: desoliture Date: Tue, 22 Feb 2022 18:00:12 +0200 Subject: [PATCH 14/14] update license headers --- .../components/profile/alarm/alarm-dynamic-value.component.html | 2 +- .../components/profile/alarm/alarm-dynamic-value.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html index 99eee091cb..6a64ba4800 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-dynamic-value.component.html @@ -1,6 +1,6 @@