diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index 133d7ed0ee..3258d5e7c8 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -41,7 +41,7 @@ CREATE TABLE IF NOT EXISTS notification_template ( created_time BIGINT NOT NULL, tenant_id UUID NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, - notification_type VARCHAR(32) NOT NULL, + notification_type VARCHAR(50) NOT NULL, configuration VARCHAR(10000) NOT NULL, CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name) ); @@ -84,7 +84,7 @@ CREATE TABLE IF NOT EXISTS notification ( created_time BIGINT NOT NULL, request_id UUID NULL CONSTRAINT fk_notification_request_id REFERENCES notification_request(id) ON DELETE CASCADE, recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE, - type VARCHAR(32) NOT NULL, + type VARCHAR(50) NOT NULL, subject VARCHAR(255), text VARCHAR(1000) NOT NULL, additional_config VARCHAR(1000), diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 5b8fe03e92..47b83c1c36 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -95,6 +95,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; +import org.thingsboard.server.service.notification.rule.NotificationRuleProcessingService; import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; @@ -334,6 +335,10 @@ public class ActorSystemContext { @Getter private NotificationCenter notificationCenter; + @Autowired + @Getter + private NotificationRuleProcessingService notificationRuleProcessingService; + @Autowired @Getter private SlackService slackService; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java index ff9e21dedd..def1d13029 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java @@ -20,7 +20,6 @@ import org.thingsboard.server.actors.TbActor; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorId; import org.thingsboard.server.actors.TbEntityActorId; -import org.thingsboard.server.actors.service.ComponentActor; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; @@ -30,7 +29,7 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; -public class RuleChainActor extends ComponentActor { +public class RuleChainActor extends RuleEngineComponentActor { private final RuleChain ruleChain; @@ -101,6 +100,16 @@ public class RuleChainActor extends ComponentActor> extends ComponentActor { + + public RuleEngineComponentActor(ActorSystemContext systemContext, TenantId tenantId, T id) { + super(systemContext, tenantId, id); + } + + @Override + protected void logLifecycleEvent(ComponentLifecycleEvent event, Exception e) { + super.logLifecycleEvent(event, e); + systemContext.getNotificationRuleProcessingService().process(tenantId, getRuleChainId(), getRuleChainName(), + id, processor.getComponentName(), event, e); + } + + protected abstract RuleChainId getRuleChainId(); + + protected abstract String getRuleChainName(); + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java index f5847f764b..1af550a53b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java @@ -21,7 +21,6 @@ import org.thingsboard.server.actors.TbActor; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorId; import org.thingsboard.server.actors.TbEntityActorId; -import org.thingsboard.server.actors.service.ComponentActor; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -32,7 +31,7 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; @Slf4j -public class RuleNodeActor extends ComponentActor { +public class RuleNodeActor extends RuleEngineComponentActor { private final String ruleChainName; private final RuleChainId ruleChainId; @@ -133,6 +132,16 @@ public class RuleNodeActor extends ComponentActor target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { - throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing"); + if (notificationRequest.getRuleId() == null) { + if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { + throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing"); + } } }); @@ -204,15 +205,17 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple log.debug("[{}] Processing notification request for {} target ({}) for delivery methods {}", ctx.getRequest().getId(), target.getConfiguration().getType(), target.getId(), deliveryMethods); List> results = new ArrayList<>(); - for (NotificationRecipient recipient : recipients) { - for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { - ListenableFuture resultFuture = processForRecipient(deliveryMethod, recipient, ctx); - DonAsynchron.withCallback(resultFuture, result -> { - ctx.getStats().reportSent(deliveryMethod, recipient); - }, error -> { - ctx.getStats().reportError(deliveryMethod, error, recipient); - }); - results.add(resultFuture); + if (!deliveryMethods.isEmpty()) { + for (NotificationRecipient recipient : recipients) { + for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { + ListenableFuture resultFuture = processForRecipient(deliveryMethod, recipient, ctx); + DonAsynchron.withCallback(resultFuture, result -> { + ctx.getStats().reportSent(deliveryMethod, recipient); + }, error -> { + ctx.getStats().reportError(deliveryMethod, error, recipient); + }); + results.add(resultFuture); + } } } return results; @@ -333,6 +336,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple // marking related notifications as unread: FIXME: causes each subscription to fetch notifications on each request update notificationService.updateNotificationsStatusByRequestId(tenantId, notificationRequest.getId(), NotificationStatus.SENT); + // TODO: no need to update request with other than PLATFORM_USERS target type onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() .notificationRequestId(notificationRequest.getId()) .notificationInfo(notificationRequest.getInfo()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java index a931440a3d..d1f3c5ea2f 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; @@ -45,7 +46,9 @@ import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.NotificationExecutorService; +import org.thingsboard.server.service.notification.rule.trigger.AlarmTriggerProcessor.AlarmTriggerObject; import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor; +import org.thingsboard.server.service.notification.rule.trigger.RuleEngineComponentLifecycleEventTriggerProcessor.RuleEngineComponentLifecycleEventTriggerObject; import java.util.Collection; import java.util.List; @@ -71,7 +74,9 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul DataConstants.INACTIVITY_EVENT, NotificationRuleTriggerType.DEVICE_INACTIVITY, DataConstants.ENTITY_CREATED, NotificationRuleTriggerType.ENTITY_ACTION, DataConstants.ENTITY_UPDATED, NotificationRuleTriggerType.ENTITY_ACTION, - DataConstants.ENTITY_DELETED, NotificationRuleTriggerType.ENTITY_ACTION + DataConstants.ENTITY_DELETED, NotificationRuleTriggerType.ENTITY_ACTION, + DataConstants.COMMENT_CREATED, NotificationRuleTriggerType.ALARM_COMMENT, + DataConstants.COMMENT_UPDATED, NotificationRuleTriggerType.ALARM_COMMENT ); @Override @@ -82,37 +87,52 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul return; } - processTrigger(tenantId, triggerType, ruleEngineMsg.getOriginator(), ruleEngineMsg, false); + processTrigger(tenantId, triggerType, ruleEngineMsg.getOriginator(), ruleEngineMsg); } @Override public void process(TenantId tenantId, Alarm alarm, boolean deleted) { - processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarm.getId(), alarm, deleted); + AlarmTriggerObject triggerObject = AlarmTriggerObject.builder() + .alarm(alarm) + .deleted(deleted) + .build(); + processTrigger(tenantId, NotificationRuleTriggerType.ALARM, alarm.getId(), triggerObject); + } + + @Override + public void process(TenantId tenantId, RuleChainId ruleChainId, String ruleChainName, EntityId componentId, String componentName, ComponentLifecycleEvent eventType, Exception error) { + RuleEngineComponentLifecycleEventTriggerObject triggerObject = RuleEngineComponentLifecycleEventTriggerObject.builder() + .ruleChainId(ruleChainId) + .ruleChainName(ruleChainName) + .componentId(componentId) + .componentName(componentName) + .eventType(eventType) + .error(error) + .build(); + processTrigger(tenantId, NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, componentId, triggerObject); } - private void processTrigger(TenantId tenantId, NotificationRuleTriggerType triggerType, EntityId originatorEntityId, - Object triggerObject, boolean triggerRemoved) { + private void processTrigger(TenantId tenantId, NotificationRuleTriggerType triggerType, EntityId originatorEntityId, Object triggerObject) { ListenableFuture> rulesFuture = dbCallbackExecutor.submit(() -> { return notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType); }); DonAsynchron.withCallback(rulesFuture, rules -> { for (NotificationRule rule : rules) { notificationExecutor.submit(() -> { - processNotificationRule(rule, originatorEntityId, triggerObject, triggerRemoved); + processNotificationRule(rule, originatorEntityId, triggerObject); }); } }, e -> {}); } - private void processNotificationRule(NotificationRule rule, EntityId originatorEntityId, - T triggerObject, boolean triggerRemoved) { + private void processNotificationRule(NotificationRule rule, EntityId originatorEntityId, Object triggerObject) { NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig(); log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType()); if (triggerConfig.getTriggerType().isUpdatable()) { List notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), originatorEntityId); if (!notificationRequests.isEmpty()) { - if (triggerRemoved || matchesClearRule(triggerObject, triggerConfig)) { + if (matchesClearRule(triggerObject, triggerConfig)) { notificationRequests = notificationRequests.stream() .filter(notificationRequest -> { if (!notificationRequest.isSent()) { @@ -133,7 +153,6 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul NotificationInfo previousNotificationInfo = notificationRequest.getInfo(); if (!notificationInfo.equals(previousNotificationInfo)) { notificationRequest.setInfo(notificationInfo); - // and make notifications unread ? dbCallbackExecutor.submit(() -> { notificationCenter.updateNotificationRequest(rule.getTenantId(), notificationRequest); }); diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java index 94f0f1724f..c22b4e09d6 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/NotificationRuleProcessingService.java @@ -16,7 +16,10 @@ package org.thingsboard.server.service.notification.rule; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbMsg; public interface NotificationRuleProcessingService { @@ -25,4 +28,7 @@ public interface NotificationRuleProcessingService { void process(TenantId tenantId, Alarm alarm, boolean deleted); + void process(TenantId tenantId, RuleChainId ruleChainId, String ruleChainName, + EntityId componentId, String componentName, ComponentLifecycleEvent eventType, Exception error); + } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java new file mode 100644 index 0000000000..de578aa233 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2022 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. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmComment; +import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo; +import org.thingsboard.server.common.data.notification.info.NotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.msg.TbMsg; + +@Service +public class AlarmCommentTriggerProcessor implements NotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) { + return ruleEngineMsg.getMetaData().getValue("comment") != null; + } + + @Override + public NotificationInfo constructNotificationInfo(TbMsg ruleEngineMsg, AlarmCommentNotificationRuleTriggerConfig triggerConfig) { + AlarmComment comment = JacksonUtil.fromString(ruleEngineMsg.getMetaData().getValue("comment"), AlarmComment.class); + Alarm alarm = JacksonUtil.fromString(ruleEngineMsg.getData(), Alarm.class); + return AlarmCommentNotificationInfo.builder() + .comment(comment.getComment().get("text").asText()) + .alarmType(alarm.getType()) + .alarmId(comment.getAlarmId().getId()) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ALARM_COMMENT; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmNotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java similarity index 70% rename from application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmNotificationRuleTriggerProcessor.java rename to application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java index ec6c414fc3..df942dc7f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmNotificationRuleTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.notification.rule.trigger; +import lombok.Builder; +import lombok.Data; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.alarm.Alarm; @@ -23,18 +25,24 @@ import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.service.notification.rule.trigger.AlarmTriggerProcessor.AlarmTriggerObject; @Service -public class AlarmNotificationRuleTriggerProcessor implements NotificationRuleTriggerProcessor { +public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor { @Override - public boolean matchesFilter(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) { + public boolean matchesFilter(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) { + Alarm alarm = triggerObject.getAlarm(); return (CollectionUtils.isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && (CollectionUtils.isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())); } @Override - public boolean matchesClearRule(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) { + public boolean matchesClearRule(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) { + if (triggerObject.isDeleted()) { + return true; + } + Alarm alarm = triggerObject.getAlarm(); ClearRule clearRule = triggerConfig.getClearRule(); if (clearRule != null) { if (clearRule.getAlarmStatus() != null) { @@ -45,7 +53,8 @@ public class AlarmNotificationRuleTriggerProcessor implements NotificationRuleTr } @Override - public NotificationInfo constructNotificationInfo(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) { + public NotificationInfo constructNotificationInfo(AlarmTriggerObject triggerObject, AlarmNotificationRuleTriggerConfig triggerConfig) { + Alarm alarm = triggerObject.getAlarm(); return AlarmNotificationInfo.builder() .alarmId(alarm.getUuidId()) .alarmType(alarm.getType()) @@ -61,4 +70,11 @@ public class AlarmNotificationRuleTriggerProcessor implements NotificationRuleTr return NotificationRuleTriggerType.ALARM; } + @Data + @Builder + public static class AlarmTriggerObject { + private final Alarm alarm; + private final boolean deleted; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityTriggerProcessor.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java rename to application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityTriggerProcessor.java index abbbbda58a..ed178e699b 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityTriggerProcessor.java @@ -30,7 +30,7 @@ import org.thingsboard.server.service.profile.TbDeviceProfileCache; @Service @RequiredArgsConstructor -public class DeviceInactivityNotificationRuleTriggerProcessor implements NotificationRuleTriggerProcessor { +public class DeviceInactivityTriggerProcessor implements NotificationRuleTriggerProcessor { private final TbDeviceProfileCache deviceProfileCache; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionNotificationRuleTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionNotificationRuleTriggerProcessor.java rename to application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java index ea1c6da5ee..9e4a595e3d 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionNotificationRuleTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.msg.TbMsg; import java.util.UUID; @Service -public class EntityActionNotificationRuleTriggerProcessor implements NotificationRuleTriggerProcessor { +public class EntityActionTriggerProcessor implements NotificationRuleTriggerProcessor { @Override public boolean matchesFilter(TbMsg ruleEngineMsg, EntityActionNotificationRuleTriggerConfig triggerConfig) { diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java new file mode 100644 index 0000000000..7a77b3d8ed --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java @@ -0,0 +1,110 @@ +/** + * Copyright © 2016-2022 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. + */ +package org.thingsboard.server.service.notification.rule.trigger; + +import com.google.common.base.Strings; +import lombok.Builder; +import lombok.Data; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.notification.info.NotificationInfo; +import org.thingsboard.server.common.data.notification.info.RuleEngineComponentLifecycleEventNotificationInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.service.notification.rule.trigger.RuleEngineComponentLifecycleEventTriggerProcessor.RuleEngineComponentLifecycleEventTriggerObject; + +import java.util.Set; + +@Service +public class RuleEngineComponentLifecycleEventTriggerProcessor implements NotificationRuleTriggerProcessor { + + @Override + public boolean matchesFilter(RuleEngineComponentLifecycleEventTriggerObject triggerObject, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) { + if (CollectionUtils.isNotEmpty(triggerConfig.getRuleChains())) { + if (!triggerConfig.getRuleChains().contains(triggerObject.getRuleChainId().getId())) { + return false; + } + } + + EntityType componentType = triggerObject.getComponentId().getEntityType(); + Set trackedEvents; + boolean onlyFailures; + if (componentType == EntityType.RULE_CHAIN) { + trackedEvents = triggerConfig.getRuleChainEvents(); + onlyFailures = triggerConfig.isOnlyRuleChainLifecycleFailures(); + } else if (componentType == EntityType.RULE_NODE && triggerConfig.isTrackRuleNodeEvents()) { + trackedEvents = triggerConfig.getRuleNodeEvents(); + onlyFailures = triggerConfig.isOnlyRuleNodeLifecycleFailures(); + } else { + return false; + } + if (CollectionUtils.isEmpty(trackedEvents)) { + trackedEvents = Set.of(ComponentLifecycleEvent.STARTED, ComponentLifecycleEvent.UPDATED, ComponentLifecycleEvent.STOPPED); + } + + if (!trackedEvents.contains(triggerObject.getEventType())) { + return false; + } + if (onlyFailures) { + return triggerObject.getError() != null; + } + return true; + } + + @Override + public NotificationInfo constructNotificationInfo(RuleEngineComponentLifecycleEventTriggerObject triggerObject, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) { + return RuleEngineComponentLifecycleEventNotificationInfo.builder() + .ruleChainId(triggerObject.getRuleChainId()) + .ruleChainName(triggerObject.getRuleChainName()) + .componentId(triggerObject.getComponentId()) + .componentName(triggerObject.getComponentName()) + .eventType(triggerObject.getEventType()) + .error(getErrorMsg(triggerObject.getError())) + .build(); + } + + private String getErrorMsg(Exception error) { + String errorMsg = error != null ? error.getMessage() : null; + errorMsg = Strings.nullToEmpty(errorMsg); + int lengthLimit = 150; + if (errorMsg.length() > lengthLimit) { + errorMsg = StringUtils.substring(errorMsg, 0, lengthLimit + 1).trim() + "[...]"; + } + return errorMsg; + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT; + } + + @Data + @Builder + public static class RuleEngineComponentLifecycleEventTriggerObject { + private final RuleChainId ruleChainId; + private final String ruleChainName; + private final EntityId componentId; + private final String componentName; + private final ComponentLifecycleEvent eventType; + private final Exception error; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index afa899e4d8..e2be8b081e 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -49,6 +50,7 @@ import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; +import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; @@ -64,7 +66,7 @@ import java.util.Optional; public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { private final AlarmService alarmService; - private final AlarmCommentService alarmCommentService; + private final TbAlarmCommentService alarmCommentService; private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageStateService apiUsageStateService; @@ -85,7 +87,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService .type(AlarmCommentType.SYSTEM) .comment(JacksonUtil.newObjectNode().put("text", String.format("Alarm severity was updated from %s to %s", oldSeverity, result.getAlarm().getSeverity()))) .build(); - alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment); + try { + alarmCommentService.saveAlarmComment(alarm, alarmComment, null); + } catch (ThingsboardException e) { + log.error("Failed to save alarm comment", e); + } } } if (result.isCreated()) { diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index b61d87a0af..6c3aa1e2f0 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -24,6 +24,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.data.util.Pair; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode; +import org.thingsboard.rule.engine.debug.TbMsgGeneratorNodeConfiguration; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -40,6 +42,7 @@ 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.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.id.NotificationRuleId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; @@ -53,13 +56,19 @@ import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; +import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.EntityKeyValueType; 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.common.data.script.ScriptLanguage; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.notification.NotificationRequestService; @@ -130,7 +139,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { getWsClient().waitForUpdate(true); Notification notification = getWsClient().getLastDataUpdate().getUpdate(); - assertThat(notification.getSubject()).isEqualTo("ADDED: DEVICE [" + device.getId() + "]"); + assertThat(notification.getSubject()).isEqualTo("added: DEVICE [" + device.getId() + "]"); assertThat(notification.getText()).isEqualTo("User: " + TENANT_ADMIN_EMAIL); @@ -140,7 +149,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { getWsClient().waitForUpdate(true); notification = getWsClient().getLastDataUpdate().getUpdate(); - assertThat(notification.getSubject()).isEqualTo("UPDATED: DEVICE [" + device.getId() + "]"); + assertThat(notification.getSubject()).isEqualTo("updated: DEVICE [" + device.getId() + "]"); getWsClient().registerWaitForUpdate(); @@ -148,7 +157,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { getWsClient().waitForUpdate(true); notification = getWsClient().getLastDataUpdate().getUpdate(); - assertThat(notification.getSubject()).isEqualTo("DELETED: DEVICE [" + device.getId() + "]"); + assertThat(notification.getSubject()).isEqualTo("deleted: DEVICE [" + device.getId() + "]"); } @Test @@ -307,6 +316,48 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { assertThat(findNotificationRequests(EntityType.ALARM).getData()).filteredOn(NotificationRequest::isScheduled).isEmpty(); } + @Test + public void testNotificationRuleProcessing_ruleEngineComponentLifecycleEvent_ruleNodeStartError() { + String subject = "Rule Node '${componentName}' in Rule Chain '${ruleChainName}' failed to start"; + String text = "The error: ${error}"; + NotificationTemplate template = createNotificationTemplate(NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, subject, text, NotificationDeliveryMethod.PUSH); + + NotificationRule rule = new NotificationRule(); + rule.setName("Rule node start-up failures in my rule chain"); + rule.setTemplateId(template.getId()); + rule.setTriggerType(NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT); + + RuleChain ruleChain = createEmptyRuleChain("My Rule Chain"); + var triggerConfig = new RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig(); + triggerConfig.setRuleChains(Set.of(ruleChain.getUuidId())); + triggerConfig.setRuleChainEvents(Set.of(ComponentLifecycleEvent.STARTED)); + triggerConfig.setOnlyRuleChainLifecycleFailures(true); + + triggerConfig.setTrackRuleNodeEvents(true); + triggerConfig.setRuleNodeEvents(Set.of(ComponentLifecycleEvent.STARTED)); + triggerConfig.setOnlyRuleNodeLifecycleFailures(true); + rule.setTriggerConfig(triggerConfig); + + NotificationTarget target = createNotificationTarget(tenantAdminUserId); + DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); + recipientsConfig.setTriggerType(NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT); + recipientsConfig.setTargets(List.of(target.getUuidId())); + rule.setRecipientsConfig(recipientsConfig); + rule = saveNotificationRule(rule); + + getWsClient().subscribeForUnreadNotifications(10).waitForReply(true); + getWsClient().registerWaitForUpdate(); + + addRuleNodeWithError(ruleChain.getId(), "My generator"); + + getWsClient().waitForUpdate(10000, true); + Notification notification = getWsClient().getLastDataUpdate().getUpdate(); + + assertThat(notification.getType()).isEqualTo(NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT); + assertThat(notification.getSubject()).isEqualTo("Rule Node 'My generator' in Rule Chain 'My Rule Chain' failed to start"); + assertThat(notification.getText()).startsWith("The error: Can't compile script"); + } + @Test public void testNotificationRuleInfo() throws Exception { NotificationDeliveryMethod[] deliveryMethods = {NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL}; @@ -368,6 +419,41 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { return deviceProfile; } + private RuleChain createEmptyRuleChain(String name) { + RuleChain ruleChain = new RuleChain(); + ruleChain.setName(name); + ruleChain.setTenantId(tenantId); + ruleChain.setRoot(false); + ruleChain.setDebugMode(false); + ruleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); + + RuleChainMetaData metaData = new RuleChainMetaData(); + metaData.setRuleChainId(ruleChain.getId()); + metaData.setNodes(List.of()); + metaData = doPost("/api/ruleChain/metadata", metaData, RuleChainMetaData.class); + return ruleChain; + } + + private RuleNode addRuleNodeWithError(RuleChainId ruleChainId, String name) { + RuleChainMetaData metaData = new RuleChainMetaData(); + metaData.setRuleChainId(ruleChainId); + + RuleNode generatorNodeWithError = new RuleNode(); + generatorNodeWithError.setName(name); + generatorNodeWithError.setType(TbMsgGeneratorNode.class.getName()); + TbMsgGeneratorNodeConfiguration generatorNodeConfiguration = new TbMsgGeneratorNodeConfiguration(); + generatorNodeConfiguration.setScriptLang(ScriptLanguage.JS); + generatorNodeConfiguration.setPeriodInSeconds(1000); + generatorNodeConfiguration.setMsgCount(1); + generatorNodeConfiguration.setJsScript("[return"); + generatorNodeWithError.setConfiguration(mapper.valueToTree(generatorNodeConfiguration)); + + metaData.setNodes(List.of(generatorNodeWithError)); + metaData.setFirstNodeIndex(0); + metaData = doPost("/api/ruleChain/metadata", metaData, RuleChainMetaData.class); + return metaData.getNodes().get(0); + } + private NotificationRule saveNotificationRule(NotificationRule notificationRule) { return doPost("/api/notification/rule", notificationRule, NotificationRule.class); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 970fd6177e..df3418368c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -75,6 +75,8 @@ public class DataConstants { public static final String ALARM_ACK = "ALARM_ACK"; public static final String ALARM_CLEAR = "ALARM_CLEAR"; public static final String ALARM_DELETE = "ALARM_DELETE"; + public static final String COMMENT_CREATED = "COMMENT_CREATED"; + public static final String COMMENT_UPDATED = "COMMENT_UPDATED"; public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java index 9cdda924d6..2911ca2a69 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationProcessingContext.java @@ -15,6 +15,9 @@ */ package org.thingsboard.server.common.data.notification; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; import com.google.common.base.Strings; import lombok.Builder; import lombok.Getter; @@ -22,7 +25,6 @@ import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; @@ -31,10 +33,13 @@ import org.thingsboard.server.common.data.notification.template.DeliveryMethodNo import org.thingsboard.server.common.data.notification.template.HasSubject; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; +import org.thingsboard.server.common.data.notification.template.PushDeliveryMethodNotificationTemplate; +import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; @SuppressWarnings("unchecked") @@ -89,10 +94,11 @@ public class NotificationProcessingContext { return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod); } - public T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map templateContext) { - if (request.getInfo() != null && deliveryMethod != NotificationDeliveryMethod.PUSH) { // for push notifications we are processing template from info on each serialization + public T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map templateContext) { + NotificationInfo info = request.getInfo(); + if (info != null && deliveryMethod != NotificationDeliveryMethod.PUSH) { // for push notifications we are processing template from info on each serialization templateContext = new HashMap<>(templateContext); - templateContext.putAll(request.getInfo().getTemplateData()); + templateContext.putAll(info.getTemplateData()); } T template = (T) templates.get(deliveryMethod).copy(); @@ -101,14 +107,30 @@ public class NotificationProcessingContext { String subject = ((HasSubject) template).getSubject(); ((HasSubject) template).setSubject(processTemplate(subject, templateContext)); } + + if (deliveryMethod == NotificationDeliveryMethod.PUSH) { + PushDeliveryMethodNotificationTemplate pushNotificationTemplate = (PushDeliveryMethodNotificationTemplate) template; + Optional buttonConfig = Optional.ofNullable(pushNotificationTemplate.getAdditionalConfig()) + .map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject) + .map(config -> (ObjectNode) config); + if (buttonConfig.isPresent()) { + JsonNode link = buttonConfig.get().get("link"); + if (link != null && link.isTextual()) { + link = new TextNode(processTemplate(link.asText(), templateContext, info != null ? info.getTemplateData() : Collections.emptyMap())); + buttonConfig.get().set("link", link); + } + } + } return template; } - private static String processTemplate(String template, Map context) { - if (template == null || context.isEmpty()) return template; + private static String processTemplate(String template, Map... contexts) { + if (template == null) return null; String result = template; - for (Map.Entry kv : context.entrySet()) { - result = result.replace("${" + kv.getKey() + '}', kv.getValue()); + for (Map context : contexts) { + for (Map.Entry kv : context.entrySet()) { + result = result.replace("${" + kv.getKey() + '}', kv.getValue()); + } } return result; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java index b5023ee42e..7080304a5e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java @@ -20,6 +20,8 @@ public enum NotificationType { GENERAL, ALARM, DEVICE_INACTIVITY, - ENTITY_ACTION; + ENTITY_ACTION, + ALARM_COMMENT, + RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java new file mode 100644 index 0000000000..590b2a2fee --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2022 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. + */ +package org.thingsboard.server.common.data.notification.info; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class AlarmCommentNotificationInfo implements NotificationInfo { + + private String comment; + private String alarmType; + private UUID alarmId; + + @Override + public Map getTemplateData() { + return Map.of( + "comment", comment, + "alarmType", alarmType, + "alarmId", alarmId.toString() + ); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java index 41b51450fc..6533f0e0f2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java @@ -51,7 +51,7 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI "entityType", entityType.name(), "entityId", entityId.toString(), "entityName", entityName, - "actionType", actionType.name(), + "actionType", actionType.name().toLowerCase(), "originatorUserId", originatorUserId.toString(), "originatorUserName", originatorUserName ); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java new file mode 100644 index 0000000000..d2006c7098 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2022 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. + */ +package org.thingsboard.server.common.data.notification.info; + +import com.google.common.base.Strings; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; + +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RuleEngineComponentLifecycleEventNotificationInfo implements NotificationInfo { + + private RuleChainId ruleChainId; + private String ruleChainName; + private EntityId componentId; + private String componentName; + private ComponentLifecycleEvent eventType; + private String error; + + @Override + public Map getTemplateData() { + return Map.of( + "ruleChainId", ruleChainId.toString(), + "ruleChainName", ruleChainName, + "componentId", componentId.toString(), + "componentType", componentId.getEntityType().name(), + "componentName", componentName, + "eventType", eventType.name(), + "error", error + ); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentNotificationRuleTriggerConfig.java new file mode 100644 index 0000000000..9e4ecba6cc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmCommentNotificationRuleTriggerConfig.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 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. + */ +package org.thingsboard.server.common.data.notification.rule.trigger; + +import lombok.Data; + +@Data +public class AlarmCommentNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ALARM_COMMENT; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java index 5c711ffd05..c6a02f0a72 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java @@ -25,7 +25,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonSubTypes({ @Type(value = AlarmNotificationRuleTriggerConfig.class, name = "ALARM"), @Type(value = DeviceInactivityNotificationRuleTriggerConfig.class, name = "DEVICE_INACTIVITY"), - @Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION") + @Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION"), + @Type(value = AlarmCommentNotificationRuleTriggerConfig.class, name = "ALARM_COMMENT"), + @Type(value = RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.class, name = "RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT") }) public interface NotificationRuleTriggerConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java index 2cb82ec07e..47c6e067ca 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java @@ -22,10 +22,12 @@ import lombok.RequiredArgsConstructor; public enum NotificationRuleTriggerType { ALARM(true), + ALARM_COMMENT(true), DEVICE_INACTIVITY(false), - ENTITY_ACTION(false); + ENTITY_ACTION(false), + RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT(false); @Getter - private final boolean isUpdatable; + private final boolean updatable; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.java new file mode 100644 index 0000000000..b34732ae30 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2022 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. + */ +package org.thingsboard.server.common.data.notification.rule.trigger; + +import lombok.Data; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; + +import java.util.Set; +import java.util.UUID; + +@Data +public class RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { + + private Set ruleChainEvents; // available options: STARTED, UPDATED, STOPPED. if empty - all events + private boolean onlyRuleChainLifecycleFailures; + + private boolean trackRuleNodeEvents; + private Set ruleNodeEvents; // available options: STARTED, UPDATED, STOPPED. if empty - all events + private boolean onlyRuleNodeLifecycleFailures; + + private Set ruleChains; // if empty - all rule chains + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT; + } + +} diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index a64fa398cd..b9c3225a5d 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -807,7 +807,7 @@ CREATE TABLE IF NOT EXISTS notification_template ( created_time BIGINT NOT NULL, tenant_id UUID NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, - notification_type VARCHAR(32) NOT NULL, + notification_type VARCHAR(50) NOT NULL, configuration VARCHAR(10000) NOT NULL, CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name) ); @@ -845,7 +845,7 @@ CREATE TABLE IF NOT EXISTS notification ( created_time BIGINT NOT NULL, request_id UUID NULL CONSTRAINT fk_notification_request_id REFERENCES notification_request(id) ON DELETE CASCADE, recipient_id UUID NOT NULL CONSTRAINT fk_notification_recipient_id REFERENCES tb_user(id) ON DELETE CASCADE, - type VARCHAR(32) NOT NULL, + type VARCHAR(50) NOT NULL, subject VARCHAR(255), text VARCHAR(1000) NOT NULL, additional_config VARCHAR(1000), diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html index d37f976cf9..4dae0d0acd 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.html @@ -198,6 +198,29 @@ + + + {{ 'notification.alarm-comment-trigger-settings' | translate }} +
+
+ + +
+
+
+
+ + notification.description + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts index d1634e0d4e..b87e915e4e 100644 --- a/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/notification-center/rule-table/rule-notification-dialog.component.ts @@ -56,6 +56,7 @@ export class RuleNotificationDialogComponent extends alarmTemplateForm: FormGroup; deviceInactivityTemplateForm: FormGroup; entityActionTemplateForm: FormGroup; + alarmCommentTemplateForm: FormGroup; triggerType = TriggerType; triggerTypes: TriggerType[] = Object.values(TriggerType); @@ -154,8 +155,15 @@ export class RuleNotificationDialogComponent extends }) }); + this.alarmCommentTemplateForm = this.fb.group({ + recipientsConfig: this.fb.group({ + targets: [[], Validators.required] + }) + }); + this.triggerTypeFormsMap = new Map([ [TriggerType.ALARM, this.alarmTemplateForm], + [TriggerType.ALARM_COMMENT, this.alarmCommentTemplateForm], [TriggerType.DEVICE_INACTIVITY, this.deviceInactivityTemplateForm], [TriggerType.ENTITY_ACTION, this.entityActionTemplateForm] ]); diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index ec0609f15a..17e99b863a 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -277,7 +277,8 @@ export enum NotificationType { GENERAL = 'GENERAL', ALARM = 'ALARM', DEVICE_INACTIVITY = 'DEVICE_INACTIVITY', - ENTITY_ACTION = 'ENTITY_ACTION' + ENTITY_ACTION = 'ENTITY_ACTION', + ALARM_COMMENT = 'ALARM_COMMENT' } interface NotificationTemplateTypeTranslate { @@ -309,17 +310,25 @@ export const NotificationTemplateTypeTranslateMap = new Map([ [TriggerType.ALARM, 'notification.trigger.alarm'], [TriggerType.DEVICE_INACTIVITY, 'notification.trigger.device-inactivity'], - [TriggerType.ENTITY_ACTION, 'notification.trigger.entity-action'] + [TriggerType.ENTITY_ACTION, 'notification.trigger.entity-action'], + [TriggerType.ALARM_COMMENT, 'notification.trigger.alarm-comment'] ]); 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 d93531e9d7..2dfcc810d4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2690,6 +2690,7 @@ "add-rule": "Add rule", "add-target": "Add recipient", "add-template": "Add template", + "alarm-comment-trigger-settings": "Alarm comment trigger settings", "alarm-trigger-settings": "Alarm trigger settings", "all": "All", "at-least-one-should-be-selected": "At least one should be selected", @@ -2816,6 +2817,7 @@ "template": "Template", "template-hint": { "alarm": "Available params: ${alarmType}, ${alarmSeverity}, ${alarmStatus}, ${alarmOriginatorEntityType}, ${alarmOriginatorId}, ${alarmId}", + "alarm-comment": "Available params: ${comment}, ${alarmType}, ${alarmId}", "device-inactivity": "Available params: ${deviceName}, ${deviceType}, ${deviceId}", "entity-action": "Available params: ${actionType}, ${entityType}, ${entityName}, ${entityId}, ${originatorUserName}, ${originatorUserId}", "general": "Available params: ${recipientEmail}, ${recipientFirstName}, ${recipientLastName}" @@ -2824,6 +2826,7 @@ "template-required": "Template is required", "template-type": { "alarm": "Alarm", + "alarm-comment": "Alarm comment", "device-inactivity": "Device inactivity", "entity-action": "Entity action", "general": "General" @@ -2833,6 +2836,7 @@ "time": "Time", "trigger": { "alarm": "Alarm", + "alarm-comment": "Alarm comment", "device-inactivity": "Device inactivity", "entity-action": "Entity action", "trigger": "Trigger",