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..1cb94bd53b 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 @@ -101,6 +101,11 @@ public class RuleChainActor 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..a12e67d8b7 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,51 @@ 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, EntityId componentId, String componentName, ComponentLifecycleEvent eventType, Exception error) { + RuleEngineComponentLifecycleEventTriggerObject triggerObject = RuleEngineComponentLifecycleEventTriggerObject.builder() + .ruleChainId(ruleChainId) + .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 +152,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..92246a6562 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,6 @@ public interface NotificationRuleProcessingService { void process(TenantId tenantId, Alarm alarm, boolean deleted); + void process(TenantId tenantId, RuleChainId ruleChainId, 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..8958aafa01 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java @@ -0,0 +1,97 @@ +/** + * 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 lombok.Builder; +import lombok.Data; +import org.apache.commons.collections.CollectionUtils; +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.Optional; +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()) + .componentId(triggerObject.getComponentId()) + .componentName(triggerObject.getComponentName()) + .eventType(triggerObject.getEventType()) + .error(Optional.ofNullable(triggerObject.getError()).map(Throwable::getMessage).orElse(null)) + .build(); + } + + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT; + } + + @Data + @Builder + public static class RuleEngineComponentLifecycleEventTriggerObject { + private final RuleChainId ruleChainId; + 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/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..4d16762d85 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,12 @@ 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.EnumMap; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; @SuppressWarnings("unchecked") @@ -89,7 +93,7 @@ public class NotificationProcessingContext { return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod); } - public T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map templateContext) { + 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 templateContext = new HashMap<>(templateContext); templateContext.putAll(request.getInfo().getTemplateData()); @@ -101,14 +105,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, request.getInfo().getTemplateData())); + 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..928aad6288 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java @@ -0,0 +1,54 @@ +/** + * 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.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 EntityId componentId; + private String componentName; + private ComponentLifecycleEvent eventType; + private String error; + // TODO: add rule chain name + + @Override + public Map getTemplateData() { + return Map.of( + "ruleChainId", ruleChainId.toString(), + "componentId", componentId.toString(), + "componentType", componentId.getEntityType().name(), + "componentName", componentName, + "eventType", eventType.name(), + "error", Strings.nullToEmpty(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),