Browse Source

Merge pull request #8265 from thingsboard/improvements/notification-system

Notification system improvements
feature/alarm-count-query
Andrew Shvayka 4 years ago
committed by GitHub
parent
commit
de921b4fa1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  2. 5
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java
  3. 6
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  4. 47
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  5. 2
      application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
  6. 17
      application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java
  7. 2
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  8. 7
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  9. 42
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  11. 44
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  12. 121
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  13. 13
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java
  14. 114
      application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
  15. 19
      application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
  16. 5
      application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java
  17. 10
      application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java
  18. 6
      application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java
  19. 78
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java
  20. 10
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
  21. 10
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
  22. 8
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  23. 59
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java
  24. 26
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java
  25. 6
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java
  26. 16
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java
  27. 6
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java
  28. 6
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java
  29. 15
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java
  30. 2
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java
  31. 10
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  32. 19
      application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
  33. 35
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  34. 3
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  35. 14
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  36. 9
      application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
  37. 26
      application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
  38. 10
      application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java
  39. 4
      application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
  40. 6
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  41. 81
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  42. 4
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  43. 3
      common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java
  44. 5
      common/cluster-api/src/main/proto/queue.proto
  45. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
  46. 1
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  47. 28
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleProcessingService.java
  48. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java
  49. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java
  50. 21
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java
  51. 48
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordState.java
  52. 4
      common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java
  53. 20
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  54. 8
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmApiCallResult.java
  55. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestPreview.java
  56. 14
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java
  57. 6
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java
  58. 16
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java
  59. 11
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java
  60. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java
  61. 63
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ApiUsageLimitNotificationInfo.java
  62. 14
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/DeviceActivityNotificationInfo.java
  63. 7
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntitiesLimitNotificationInfo.java
  64. 21
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java
  65. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java
  66. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java
  67. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java
  68. 11
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleOriginatedNotificationInfo.java
  69. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java
  70. 12
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ApiUsageLimitNotificationRuleTriggerConfig.java
  71. 22
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceActivityNotificationRuleTriggerConfig.java
  72. 4
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntityActionNotificationRuleTriggerConfig.java
  73. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java
  74. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java
  75. 8
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationRecipient.java
  76. 28
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java
  77. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java
  78. 6
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java
  79. 50
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java
  80. 56
      common/data/src/main/java/org/thingsboard/server/common/data/util/TemplateUtils.java
  81. 13
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/NotificationRuleProcessor.java
  82. 7
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/AlarmTrigger.java
  83. 49
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/ApiUsageLimitTrigger.java
  84. 5
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntitiesLimitTrigger.java
  85. 8
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NewPlatformVersionTrigger.java
  86. 10
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NotificationRuleTrigger.java
  87. 5
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/RuleEngineComponentLifecycleEventTrigger.java
  88. 12
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/RuleEngineMsgTrigger.java
  89. 5
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  90. 2
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java
  91. 55
      common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java
  92. 22
      common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java
  93. 1
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  94. 1
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  95. 12
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java
  96. 85
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  97. 42
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  98. 4
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java
  99. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  100. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java

4
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -71,7 +71,6 @@ import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
@ -90,6 +89,7 @@ import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.ComponentDiscoveryService;
@ -341,7 +341,7 @@ public class ActorSystemContext {
@Autowired @Autowired
@Getter @Getter
private NotificationRuleProcessingService notificationRuleProcessingService; private NotificationRuleProcessor notificationRuleProcessor;
@Autowired @Autowired
@Getter @Getter

5
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleEngineComponentActor.java

@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.dao.notification.trigger.RuleEngineComponentLifecycleEventTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineComponentLifecycleEventTrigger;
public abstract class RuleEngineComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ComponentActor<T, P> { public abstract class RuleEngineComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ComponentActor<T, P> {
@ -50,7 +50,8 @@ public abstract class RuleEngineComponentActor<T extends EntityId, P extends Com
} }
private void processNotificationRule(ComponentLifecycleEvent event, Throwable e) { private void processNotificationRule(ComponentLifecycleEvent event, Throwable e) {
systemContext.getNotificationRuleProcessingService().process(tenantId, RuleEngineComponentLifecycleEventTrigger.builder() systemContext.getNotificationRuleProcessor().process(RuleEngineComponentLifecycleEventTrigger.builder()
.tenantId(tenantId)
.ruleChainId(getRuleChainId()) .ruleChainId(getRuleChainId())
.ruleChainName(getRuleChainName()) .ruleChainName(getRuleChainName())
.componentId(id) .componentId(id)

6
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -49,6 +49,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg; import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.edge.EdgeSessionMsg; import org.thingsboard.server.common.msg.edge.EdgeSessionMsg;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
@ -209,7 +210,10 @@ public class TenantActor extends RuleChainManagerActor {
log.trace("[{}] Ack message because Rule Engine is disabled", tenantId); log.trace("[{}] Ack message because Rule Engine is disabled", tenantId);
tbMsg.getCallback().onSuccess(); tbMsg.getCallback().onSuccess();
} }
systemContext.getNotificationRuleProcessingService().process(tenantId, tbMsg); systemContext.getNotificationRuleProcessor().process(RuleEngineMsgTrigger.builder()
.tenantId(tenantId)
.msg(tbMsg)
.build());
} }
private void onRuleChainMsg(RuleChainAwareMsg msg) { private void onRuleChainMsg(RuleChainAwareMsg msg) {

47
application/src/main/java/org/thingsboard/server/controller/NotificationController.java

@ -42,10 +42,12 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview; import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -62,7 +64,6 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
@ -187,14 +188,12 @@ public class NotificationController extends BaseController {
checkEntity(notificationRequest.getId(), notificationRequest, NOTIFICATION); checkEntity(notificationRequest.getId(), notificationRequest, NOTIFICATION);
notificationRequest.setOriginatorEntityId(user.getId()); notificationRequest.setOriginatorEntityId(user.getId());
if (notificationRequest.getInfo() != null && !(notificationRequest.getInfo() instanceof UserOriginatedNotificationInfo)) { notificationRequest.setInfo(null);
throw new IllegalArgumentException("Unsupported notification info type");
}
notificationRequest.setRuleId(null); notificationRequest.setRuleId(null);
notificationRequest.setStatus(null); notificationRequest.setStatus(null);
notificationRequest.setStats(null); notificationRequest.setStats(null);
return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationCenter::processNotificationRequest); return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, (tenantId, request) -> notificationCenter.processNotificationRequest(tenantId, request, null));
} }
@PostMapping("/notification/request/preview") @PostMapping("/notification/request/preview")
@ -217,45 +216,49 @@ public class NotificationController extends BaseController {
NotificationProcessingContext tmpProcessingCtx = NotificationProcessingContext.builder() NotificationProcessingContext tmpProcessingCtx = NotificationProcessingContext.builder()
.tenantId(user.getTenantId()) .tenantId(user.getTenantId())
.request(request) .request(request)
.settings(null)
.template(template) .template(template)
.settings(null)
.build(); .build();
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = tmpProcessingCtx.getDeliveryMethods().stream() Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = tmpProcessingCtx.getDeliveryMethods().stream()
.collect(Collectors.toMap(m -> m, deliveryMethod -> { .collect(Collectors.toMap(m -> m, deliveryMethod -> {
Map<String, String> templateContext; NotificationRecipient recipient = null;
if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) { if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) {
templateContext = tmpProcessingCtx.createTemplateContext(user); recipient = userService.findUserById(user.getTenantId(), user.getId());
} else {
templateContext = Collections.emptyMap();
} }
return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, templateContext); return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, recipient);
})); }));
preview.setProcessedTemplates(processedTemplates); preview.setProcessedTemplates(processedTemplates);
// generic permission // generic permission
Set<User> recipientsPreview = new LinkedHashSet<>(); Set<String> recipientsPreview = new LinkedHashSet<>();
Map<String, Integer> recipientsCountByTarget = new HashMap<>(); Map<String, Integer> recipientsCountByTarget = new HashMap<>();
List<NotificationTarget> targets = notificationTargetService.findNotificationTargetsByTenantIdAndIds(user.getTenantId(), List<NotificationTarget> targets = notificationTargetService.findNotificationTargetsByTenantIdAndIds(user.getTenantId(),
request.getTargets().stream().map(NotificationTargetId::new).collect(Collectors.toList())); request.getTargets().stream().map(NotificationTargetId::new).collect(Collectors.toList()));
for (NotificationTarget target : targets) { for (NotificationTarget target : targets) {
int recipientsCount; int recipientsCount;
List<NotificationRecipient> recipientsPart;
if (target.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) { if (target.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) {
PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(),
target.getConfiguration(), new PageLink(recipientsPreviewSize)); (PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize));
recipientsCount = (int) recipients.getTotalElements(); recipientsCount = (int) recipients.getTotalElements();
for (User recipient : recipients.getData()) { recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList());
if (recipientsPreview.size() < recipientsPreviewSize) {
recipientsPreview.add(recipient);
} else {
break;
}
}
} else { } else {
recipientsCount = 1; recipientsCount = 1;
recipientsPart = List.of(((SlackNotificationTargetConfig) target.getConfiguration()).getConversation());
}
for (NotificationRecipient recipient : recipientsPart) {
if (recipientsPreview.size() < recipientsPreviewSize) {
recipientsPreview.add(recipient.getTitle());
} else {
break;
}
} }
recipientsCountByTarget.put(target.getName(), recipientsCount); recipientsCountByTarget.put(target.getName(), recipientsCount);
} }
preview.setRecipientsPreview(recipientsPreview); preview.setRecipientsPreview(recipientsPreview);
preview.setRecipientsCountByTarget(recipientsCountByTarget); preview.setRecipientsCountByTarget(recipientsCountByTarget);
preview.setTotalRecipientsCount(recipientsCountByTarget.values().stream().mapToInt(Integer::intValue).sum()); preview.setTotalRecipientsCount(recipientsCountByTarget.values().stream().mapToInt(Integer::intValue).sum());

2
application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java

@ -116,7 +116,7 @@ public class NotificationTargetController extends BaseController {
} }
PageLink pageLink = createPageLink(pageSize, page, null, null, null); PageLink pageLink = createPageLink(pageSize, page, null, null, null);
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, notificationTarget.getConfiguration(), pageLink); return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), (PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration(), pageLink);
} }
@GetMapping(value = "/targets", params = {"ids"}) @GetMapping(value = "/targets", params = {"ids"})

17
application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java

@ -17,6 +17,7 @@ package org.thingsboard.server.controller;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@ -136,16 +137,20 @@ public class NotificationTemplateController extends BaseController {
@GetMapping("/slack/conversations") @GetMapping("/slack/conversations")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversationType type, public List<SlackConversation> listSlackConversations(@RequestParam SlackConversationType type,
@RequestParam(required = false) String token,
@AuthenticationPrincipal SecurityUser user) { @AuthenticationPrincipal SecurityUser user) {
// generic permission // generic permission
NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId()); if (StringUtils.isEmpty(token)) {
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig) NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId());
settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK); SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
if (slackConfig == null) { settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
throw new IllegalArgumentException("Slack is not configured"); if (slackConfig == null) {
throw new IllegalArgumentException("Slack is not configured");
}
token = slackConfig.getBotToken();
} }
return slackService.listConversations(user.getTenantId(), slackConfig.getBotToken(), type); return slackService.listConversations(user.getTenantId(), token, type);
} }
} }

2
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -295,6 +295,8 @@ public class ThingsboardInstallService {
systemDataLoaderService.loadSystemWidgets(); systemDataLoaderService.loadSystemWidgets();
systemDataLoaderService.createOAuth2Templates(); systemDataLoaderService.createOAuth2Templates();
systemDataLoaderService.createQueues(); systemDataLoaderService.createQueues();
systemDataLoaderService.createDefaultNotificationConfigs();
// systemDataLoaderService.loadSystemPlugins(); // systemDataLoaderService.loadSystemPlugins();
// systemDataLoaderService.loadSystemRules(); // systemDataLoaderService.loadSystemRules();
installScripts.loadSystemLwm2mResources(); installScripts.loadSystemLwm2mResources();

7
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java

@ -142,6 +142,13 @@ public class EntityActionService {
if (user != null) { if (user != null) {
metaData.putValue("userId", user.getId().toString()); metaData.putValue("userId", user.getId().toString());
metaData.putValue("userName", user.getName()); metaData.putValue("userName", user.getName());
metaData.putValue("userEmail", user.getEmail());
if (user.getFirstName() != null) {
metaData.putValue("userFirstName", user.getFirstName());
}
if (user.getLastName() != null) {
metaData.putValue("userLastName", user.getLastName());
}
} }
if (customerId != null && !customerId.isNullUid()) { if (customerId != null && !customerId.isNullUid()) {
metaData.putValue("customerId", customerId.toString()); metaData.putValue("customerId", customerId.toString());

42
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -24,13 +24,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageRecordState;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.ApiUsageStateMailMessage;
import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
@ -53,7 +52,8 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.SchedulerUtils; import org.thingsboard.server.common.msg.tools.SchedulerUtils;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -63,6 +63,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
import org.thingsboard.server.service.telemetry.InternalTelemetryService; import org.thingsboard.server.service.telemetry.InternalTelemetryService;
@ -79,8 +80,6 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -108,8 +107,9 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
private final ApiUsageStateService apiUsageStateService; private final ApiUsageStateService apiUsageStateService;
private final TbTenantProfileCache tenantProfileCache; private final TbTenantProfileCache tenantProfileCache;
private final MailService mailService; private final MailService mailService;
private final NotificationRuleProcessingService notificationRuleProcessingService; private final NotificationRuleProcessor notificationRuleProcessor;
private final DbCallbackExecutorService dbExecutor; private final DbCallbackExecutorService dbExecutor;
private final MailExecutorService mailExecutor;
@Lazy @Lazy
@Autowired @Autowired
@ -130,8 +130,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
private final Lock updateLock = new ReentrantLock(); private final Lock updateLock = new ReentrantLock();
private final ExecutorService mailExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("api-usage-svc-mail"));
@PostConstruct @PostConstruct
public void init() { public void init() {
super.init(); super.init();
@ -340,32 +338,35 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK);
if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) { if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) {
String email = tenantService.findTenantById(state.getTenantId()).getEmail(); String email = tenantService.findTenantById(state.getTenantId()).getEmail();
if (StringUtils.isNotEmpty(email)) { result.forEach((apiFeature, stateValue) -> {
result.forEach((apiFeature, stateValue) -> { ApiUsageRecordState recordState = createApiUsageRecordState((TenantApiUsageState) state, apiFeature, stateValue);
notificationRuleProcessor.process(ApiUsageLimitTrigger.builder()
.tenantId(state.getTenantId())
.state(recordState)
.status(stateValue)
.build());
if (StringUtils.isNotEmpty(email)) {
mailExecutor.submit(() -> { mailExecutor.submit(() -> {
try { try {
mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage((TenantApiUsageState) state, apiFeature, stateValue)); mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, recordState);
} catch (ThingsboardException e) { } catch (ThingsboardException e) {
log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e); log.warn("[{}] Can't send update of the API state to tenant with provided email [{}]", state.getTenantId(), email, e);
} }
}); });
}); }
} else { });
log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId());
}
} }
} }
private ApiUsageStateMailMessage createStateMailMessage(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) { private ApiUsageRecordState createApiUsageRecordState(TenantApiUsageState state, ApiFeature apiFeature, ApiUsageStateValue stateValue) {
StateChecker checker = getStateChecker(stateValue); StateChecker checker = getStateChecker(stateValue);
for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) { for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) {
long threshold = state.getProfileThreshold(apiUsageRecordKey); long threshold = state.getProfileThreshold(apiUsageRecordKey);
long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey); long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey);
long value = state.get(apiUsageRecordKey); long value = state.get(apiUsageRecordKey);
if (checker.check(threshold, warnThreshold, value)) { if (checker.check(threshold, warnThreshold, value)) {
return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value); return new ApiUsageRecordState(apiFeature, apiUsageRecordKey, threshold, value);
} }
} }
return null; return null;
@ -377,7 +378,7 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
} else if (ApiUsageStateValue.WARNING.equals(stateValue)) { } else if (ApiUsageStateValue.WARNING.equals(stateValue)) {
return (t, wt, v) -> v < t && v >= wt; return (t, wt, v) -> v < t && v >= wt;
} else { } else {
return (t, wt, v) -> v >= t; return (t, wt, v) -> t > 0 && v >= t;
} }
} }
@ -529,8 +530,5 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
@PreDestroy @PreDestroy
private void destroy() { private void destroy() {
super.stop(); super.stop();
if (mailExecutor != null) {
mailExecutor.shutdownNow();
}
} }
} }

2
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.List; import java.util.List;

44
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -35,7 +35,7 @@ import org.thingsboard.rule.engine.api.TbEmail;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageRecordState;
import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@ -64,8 +64,6 @@ public class DefaultMailService implements MailService {
public static final String MAIL_PROP = "mail."; public static final String MAIL_PROP = "mail.";
public static final String TARGET_EMAIL = "targetEmail"; public static final String TARGET_EMAIL = "targetEmail";
public static final String UTF_8 = "UTF-8"; public static final String UTF_8 = "UTF-8";
public static final int _10K = 10000;
public static final int _1M = 1000000;
private final MessageSource messages; private final MessageSource messages;
private final Configuration freemarkerConfig; private final Configuration freemarkerConfig;
@ -335,7 +333,7 @@ public class DefaultMailService implements MailService {
} }
@Override @Override
public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException { public void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageRecordState recordState) throws ThingsboardException {
String subject = messages.getMessage("api.usage.state", null, Locale.US); String subject = messages.getMessage("api.usage.state", null, Locale.US);
Map<String, Object> model = new HashMap<>(); Map<String, Object> model = new HashMap<>();
@ -350,11 +348,11 @@ public class DefaultMailService implements MailService {
message = mergeTemplateIntoString("state.enabled.ftl", model); message = mergeTemplateIntoString("state.enabled.ftl", model);
break; break;
case WARNING: case WARNING:
model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(msg.getKey(), msg.getValue(), msg.getThreshold())); model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(recordState));
message = mergeTemplateIntoString("state.warning.ftl", model); message = mergeTemplateIntoString("state.warning.ftl", model);
break; break;
case DISABLED: case DISABLED:
model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(msg.getKey(), msg.getThreshold())); model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(recordState));
message = mergeTemplateIntoString("state.disabled.ftl", model); message = mergeTemplateIntoString("state.disabled.ftl", model);
break; break;
} }
@ -406,10 +404,10 @@ public class DefaultMailService implements MailService {
} }
} }
private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) { private String toWarningValueLabel(ApiUsageRecordState recordState) {
String valueInM = getValueAsString(value); String valueInM = recordState.getValueAsString();
String thresholdInM = getValueAsString(threshold); String thresholdInM = recordState.getThresholdAsString();
switch (key) { switch (recordState.getKey()) {
case STORAGE_DP_COUNT: case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT: case TRANSPORT_DP_COUNT:
return valueInM + " out of " + thresholdInM + " allowed data points"; return valueInM + " out of " + thresholdInM + " allowed data points";
@ -428,36 +426,26 @@ public class DefaultMailService implements MailService {
} }
} }
private String toDisabledValueLabel(ApiUsageRecordKey key, long value) { private String toDisabledValueLabel(ApiUsageRecordState recordState) {
switch (key) { switch (recordState.getKey()) {
case STORAGE_DP_COUNT: case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT: case TRANSPORT_DP_COUNT:
return getValueAsString(value) + " data points"; return recordState.getValueAsString() + " data points";
case TRANSPORT_MSG_COUNT: case TRANSPORT_MSG_COUNT:
return getValueAsString(value) + " messages"; return recordState.getValueAsString() + " messages";
case JS_EXEC_COUNT: case JS_EXEC_COUNT:
return "JavaScript functions " + getValueAsString(value) + " times"; return "JavaScript functions " + recordState.getValueAsString() + " times";
case RE_EXEC_COUNT: case RE_EXEC_COUNT:
return getValueAsString(value) + " Rule Engine messages"; return recordState.getValueAsString() + " Rule Engine messages";
case EMAIL_EXEC_COUNT: case EMAIL_EXEC_COUNT:
return getValueAsString(value) + " Email messages"; return recordState.getValueAsString() + " Email messages";
case SMS_EXEC_COUNT: case SMS_EXEC_COUNT:
return getValueAsString(value) + " SMS messages"; return recordState.getValueAsString() + " SMS messages";
default: default:
throw new RuntimeException("Not implemented!"); throw new RuntimeException("Not implemented!");
} }
} }
private String getValueAsString(long value) {
if (value > _1M && value % _1M < _10K) {
return value / _1M + "M";
} else if (value > _10K) {
return String.format("%.2fM", ((double) value) / 1000000);
} else {
return value + "";
}
}
private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email,
String subject, String message, long timeout) throws ThingsboardException { String subject, String message, long timeout) throws ThingsboardException {
try { try {

121
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -22,13 +22,12 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
@ -46,7 +45,6 @@ import org.thingsboard.server.common.data.notification.settings.NotificationSett
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilterType;
import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -84,6 +82,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -103,61 +102,59 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
private final NotificationsTopicService notificationsTopicService; private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider; private final TbQueueProducerProvider producerProvider;
private final RateLimitService rateLimitService; private final RateLimitService rateLimitService;
private final MailService mailService;
private final SmsService smsService;
private Map<NotificationDeliveryMethod, NotificationChannel> channels; private Map<NotificationDeliveryMethod, NotificationChannel> channels;
@Override @Override
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest request, Consumer<NotificationRequestStats> callback) {
if (!rateLimitService.checkRateLimit(tenantId, LimitedApi.NOTIFICATION_REQUEST)) { if (!rateLimitService.checkRateLimit(tenantId, LimitedApi.NOTIFICATION_REQUEST)) {
throw new TbRateLimitsException(EntityType.TENANT); throw new TbRateLimitsException(EntityType.TENANT);
} }
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
NotificationTemplate notificationTemplate; NotificationTemplate notificationTemplate;
if (notificationRequest.getTemplateId() != null) { if (request.getTemplateId() != null) {
notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, notificationRequest.getTemplateId()); notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, request.getTemplateId());
} else { } else {
notificationTemplate = notificationRequest.getTemplate(); notificationTemplate = request.getTemplate();
} }
if (notificationTemplate == null) throw new IllegalArgumentException("Template is missing"); if (notificationTemplate == null) throw new IllegalArgumentException("Template is missing");
List<NotificationTarget> targets = notificationRequest.getTargets().stream().map(NotificationTargetId::new) List<NotificationTarget> targets = request.getTargets().stream().map(NotificationTargetId::new)
.map(id -> notificationTargetService.findNotificationTargetById(tenantId, id)).collect(Collectors.toList()); .map(id -> notificationTargetService.findNotificationTargetById(tenantId, id)).collect(Collectors.toList());
Set<NotificationDeliveryMethod> availableDeliveryMethods = getAvailableDeliveryMethods(tenantId);
NotificationRuleId ruleId = request.getRuleId();
notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (!template.isEnabled()) return; if (!template.isEnabled()) return;
if (!availableDeliveryMethods.contains(deliveryMethod)) { if (!channels.get(deliveryMethod).check(tenantId)) {
throw new IllegalArgumentException("Settings for " + deliveryMethod.getName() + " are missing"); throw new IllegalArgumentException("Unable to send notification via " + deliveryMethod.getName() + ": not configured or not working");
} }
if (notificationRequest.getRuleId() == null) { if (ruleId == null) {
if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) { if (targets.stream().noneMatch(target -> target.getConfiguration().getType().getSupportedDeliveryMethods().contains(deliveryMethod))) {
throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing"); throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing");
} }
} }
}); });
if (notificationRequest.getAdditionalConfig() != null) { if (request.getAdditionalConfig() != null) {
NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); NotificationRequestConfig config = request.getAdditionalConfig();
if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) { if (config.getSendingDelayInSec() > 0 && request.getId() == null) {
notificationRequest.setStatus(NotificationRequestStatus.SCHEDULED); request.setStatus(NotificationRequestStatus.SCHEDULED);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); request = notificationRequestService.saveNotificationRequest(tenantId, request);
forwardToNotificationSchedulerService(tenantId, savedNotificationRequest.getId()); forwardToNotificationSchedulerService(tenantId, request.getId());
return savedNotificationRequest; return request;
} }
} }
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
log.debug("Processing notification request (tenantId: {}, targets: {})", tenantId, notificationRequest.getTargets()); log.debug("Processing notification request (tenantId: {}, targets: {})", tenantId, request.getTargets());
notificationRequest.setStatus(NotificationRequestStatus.PROCESSING); request.setStatus(NotificationRequestStatus.PROCESSING);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); request = notificationRequestService.saveNotificationRequest(tenantId, request);
NotificationProcessingContext ctx = NotificationProcessingContext.builder() NotificationProcessingContext ctx = NotificationProcessingContext.builder()
.tenantId(tenantId) .tenantId(tenantId)
.request(savedNotificationRequest) .request(request)
.settings(settings)
.template(notificationTemplate) .template(notificationTemplate)
.settings(settings)
.build(); .build();
notificationExecutor.submit(() -> { notificationExecutor.submit(() -> {
@ -169,7 +166,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} }
Futures.whenAllComplete(results).run(() -> { Futures.whenAllComplete(results).run(() -> {
NotificationRequestId requestId = savedNotificationRequest.getId(); NotificationRequestId requestId = ctx.getRequest().getId();
log.debug("[{}] Notification request processing is finished", requestId); log.debug("[{}] Notification request processing is finished", requestId);
NotificationRequestStats stats = ctx.getStats(); NotificationRequestStats stats = ctx.getStats();
try { try {
@ -177,36 +174,39 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Failed to update stats for notification request", requestId, e); log.error("[{}] Failed to update stats for notification request", requestId, e);
} }
if (callback != null) {
try {
callback.accept(stats);
} catch (Exception e) {
log.error("Failed to process callback for notification request {}", requestId, e);
}
}
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
}); });
return savedNotificationRequest; return request;
} }
private List<ListenableFuture<Void>> processForTarget(NotificationTarget target, NotificationProcessingContext ctx) { private List<ListenableFuture<Void>> processForTarget(NotificationTarget target, NotificationProcessingContext ctx) {
Iterable<? extends NotificationRecipient> recipients; Iterable<? extends NotificationRecipient> recipients;
switch (target.getConfiguration().getType()) { switch (target.getConfiguration().getType()) {
case PLATFORM_USERS: { case PLATFORM_USERS: {
PlatformUsersNotificationTargetConfig platformUsersTargetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration(); PlatformUsersNotificationTargetConfig targetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration();
if (platformUsersTargetConfig.getUsersFilter().getType() == UsersFilterType.AFFECTED_USER) { if (targetConfig.getUsersFilter().getType().isForRules()) {
if (ctx.getRequest().getInfo() instanceof RuleOriginatedNotificationInfo) { recipients = new PageDataIterable<>(pageLink -> {
UserId targetUserId = ((RuleOriginatedNotificationInfo) ctx.getRequest().getInfo()).getTargetUserId(); return notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink);
if (targetUserId != null) { }, 500);
recipients = List.of(userService.findUserById(ctx.getTenantId(), targetUserId));
break;
}
}
recipients = Collections.emptyList();
} else { } else {
recipients = new PageDataIterable<>(pageLink -> { recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), ctx.getCustomerId(), platformUsersTargetConfig, pageLink); return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink);
}, 500); }, 500);
} }
break; break;
} }
case SLACK: { case SLACK: {
SlackNotificationTargetConfig slackTargetConfig = (SlackNotificationTargetConfig) target.getConfiguration(); SlackNotificationTargetConfig targetConfig = (SlackNotificationTargetConfig) target.getConfiguration();
recipients = List.of(slackTargetConfig.getConversation()); recipients = List.of(targetConfig.getConversation());
break; break;
} }
default: { default: {
@ -239,15 +239,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
if (ctx.getStats().contains(deliveryMethod, recipient.getId())) { if (ctx.getStats().contains(deliveryMethod, recipient.getId())) {
return Futures.immediateFailedFuture(new AlreadySentException()); return Futures.immediateFailedFuture(new AlreadySentException());
} }
Map<String, String> templateContext;
if (recipient instanceof User) {
templateContext = ctx.createTemplateContext(((User) recipient));
} else {
templateContext = Collections.emptyMap();
}
DeliveryMethodNotificationTemplate processedTemplate; DeliveryMethodNotificationTemplate processedTemplate;
try { try {
processedTemplate = ctx.getProcessedTemplate(deliveryMethod, templateContext); processedTemplate = ctx.getProcessedTemplate(deliveryMethod, recipient);
} catch (Exception e) { } catch (Exception e) {
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);
} }
@ -309,7 +304,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId); log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId);
NotificationUpdate update = NotificationUpdate.builder() NotificationUpdate update = NotificationUpdate.builder()
.updated(true) .updated(true)
.notificationId(notificationId) .notificationId(notificationId.getId())
.newStatus(NotificationStatus.READ) .newStatus(NotificationStatus.READ)
.build(); .build();
onNotificationUpdate(tenantId, recipientId, update); onNotificationUpdate(tenantId, recipientId, update);
@ -345,20 +340,15 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override @Override
public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) { public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) {
Set<NotificationDeliveryMethod> deliveryMethods = new HashSet<>(); return channels.values().stream()
deliveryMethods.add(NotificationDeliveryMethod.WEB); .filter(channel -> channel.check(tenantId))
NotificationSettings notificationSettings = notificationSettingsService.findNotificationSettings(tenantId); .map(NotificationChannel::getDeliveryMethod)
if (notificationSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.SLACK)) { .collect(Collectors.toSet());
deliveryMethods.add(NotificationDeliveryMethod.SLACK); }
}
try { @Override
mailService.testConnection(tenantId); public boolean check(TenantId tenantId) {
deliveryMethods.add(NotificationDeliveryMethod.EMAIL); return true;
} catch (Exception e) {}
if (smsService.isConfigured(tenantId)) {
deliveryMethods.add(NotificationDeliveryMethod.SMS);
}
return deliveryMethods;
} }
@Override @Override
@ -374,6 +364,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
.deleted(true) .deleted(true)
.build()); .build());
} else if (notificationRequest.isScheduled()) { } else if (notificationRequest.isScheduled()) {
// TODO: just forward to scheduler service
clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED); clusterService.broadcastEntityStateChangeEvent(tenantId, notificationRequestId, ComponentLifecycleEvent.DELETED);
} }
} }

13
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationSchedulerService.java

@ -25,9 +25,10 @@ import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
@ -109,14 +110,12 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS
notificationExecutor.executeAsync(() -> { notificationExecutor.executeAsync(() -> {
try { try {
notificationCenter.processNotificationRequest(tenantId, notificationRequest); notificationCenter.processNotificationRequest(tenantId, notificationRequest, null);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to process scheduled notification request {}", notificationRequest.getId(), e); log.error("Failed to process scheduled notification request {}", notificationRequest.getId(), e);
UserId senderId = notificationRequest.getSenderId(); NotificationRequestStats stats = new NotificationRequestStats();
if (senderId != null) { stats.setError(e.getMessage());
notificationCenter.sendBasicNotification(tenantId, senderId, "Notification failure", notificationRequestService.updateNotificationRequest(tenantId, request.getId(), NotificationRequestStatus.SENT, stats);
"Failed to process scheduled notification (request " + notificationRequest.getId() + "): " + e.getMessage());
}
} }
}); });
scheduledNotificationRequests.remove(notificationRequest.getId()); scheduledNotificationRequests.remove(notificationRequest.getId());

114
application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java

@ -15,35 +15,30 @@
*/ */
package org.thingsboard.server.service.notification; package org.thingsboard.server.service.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 com.google.common.base.Strings;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.collections4.MapUtils;
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.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStats;
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; import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.HasSubject; 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.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.util.TemplateUtils;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class NotificationProcessingContext { public class NotificationProcessingContext {
@ -62,11 +57,9 @@ public class NotificationProcessingContext {
@Getter @Getter
private final NotificationRequestStats stats; private final NotificationRequestStats stats;
private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\$\\{([a-zA-Z]+)(:[a-zA-Z]+)?}");
@Builder @Builder
public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationSettings settings, public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationTemplate template, NotificationSettings settings) {
NotificationTemplate template) {
this.tenantId = tenantId; this.tenantId = tenantId;
this.request = request; this.request = request;
this.settings = settings; this.settings = settings;
@ -80,6 +73,7 @@ public class NotificationProcessingContext {
NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration(); NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration();
templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> { templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (template.isEnabled()) { if (template.isEnabled()) {
template = processTemplate(template, null); // processing template with immutable params
templates.put(deliveryMethod, template); templates.put(deliveryMethod, template);
} }
}); });
@ -90,74 +84,54 @@ public class NotificationProcessingContext {
return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod); return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod);
} }
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) { public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) {
NotificationInfo info = request.getInfo(); T template = (T) templates.get(deliveryMethod);
if (info != null) { Map<String, String> additionalTemplateContext = null;
templateContext = new HashMap<>(templateContext); if (recipient != null) {
templateContext.putAll(info.getTemplateData()); additionalTemplateContext = createTemplateContextForRecipient(recipient);
}
if (MapUtils.isNotEmpty(additionalTemplateContext) && template.containsAny(additionalTemplateContext.keySet().toArray(String[]::new))) {
template = processTemplate(template, additionalTemplateContext);
} }
return template;
}
T template = (T) templates.get(deliveryMethod).copy(); private <T extends DeliveryMethodNotificationTemplate> T processTemplate(T template, Map<String, String> additionalTemplateContext) {
template.setBody(processTemplate(template.getBody(), templateContext)); Map<String, String> templateContext = new HashMap<>();
if (request.getInfo() != null) {
templateContext.putAll(request.getInfo().getTemplateData());
}
if (additionalTemplateContext != null) {
templateContext.putAll(additionalTemplateContext);
}
if (templateContext.isEmpty()) return template;
template = (T) template.copy();
template.setBody(TemplateUtils.processTemplate(template.getBody(), templateContext));
if (template instanceof HasSubject) { if (template instanceof HasSubject) {
String subject = ((HasSubject) template).getSubject(); String subject = ((HasSubject) template).getSubject();
((HasSubject) template).setSubject(processTemplate(subject, templateContext)); ((HasSubject) template).setSubject(TemplateUtils.processTemplate(subject, templateContext));
} }
if (template instanceof WebDeliveryMethodNotificationTemplate) {
if (deliveryMethod == NotificationDeliveryMethod.WEB) {
WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template; WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template;
Optional<ObjectNode> buttonConfig = Optional.ofNullable(webNotificationTemplate.getAdditionalConfig()) String buttonText = webNotificationTemplate.getButtonText();
.map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject) if (isNotEmpty(buttonText)) {
.map(config -> (ObjectNode) config); webNotificationTemplate.setButtonText(TemplateUtils.processTemplate(buttonText, templateContext));
if (buttonConfig.isPresent()) { }
JsonNode text = buttonConfig.get().get("text"); String buttonLink = webNotificationTemplate.getButtonLink();
if (text != null && text.isTextual()) { if (isNotEmpty(buttonLink)) {
text = new TextNode(processTemplate(text.asText(), templateContext)); webNotificationTemplate.setButtonLink(TemplateUtils.processTemplate(buttonLink, templateContext));
buttonConfig.get().set("text", text);
}
JsonNode link = buttonConfig.get().get("link");
if (link != null && link.isTextual()) {
link = new TextNode(processTemplate(link.asText(), templateContext));
buttonConfig.get().set("link", link);
}
} }
} }
return template; return template;
} }
private static String processTemplate(String template, Map<String, String> context) { private Map<String, String> createTemplateContextForRecipient(NotificationRecipient recipient) {
return TEMPLATE_PARAM_PATTERN.matcher(template).replaceAll(matchResult -> { return Map.of(
String key = matchResult.group(1); "recipientEmail", Strings.nullToEmpty(recipient.getEmail()),
String value = Strings.nullToEmpty(context.get(key)); "recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()),
String function = matchResult.group(2); "recipientLastName", Strings.nullToEmpty(recipient.getLastName())
if (function != null) { );
switch (function) {
case ":upperCase":
return value.toUpperCase();
case ":lowerCase":
return value.toLowerCase();
case ":capitalize":
return StringUtils.capitalize(value.toLowerCase());
}
}
return value;
});
}
public Map<String, String> createTemplateContext(User recipient) {
Map<String, String> templateContext = new HashMap<>();
templateContext.put("recipientEmail", recipient.getEmail());
templateContext.put("recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()));
templateContext.put("recipientLastName", Strings.nullToEmpty(recipient.getLastName()));
return templateContext;
}
public CustomerId getCustomerId() {
if (request.getInfo() instanceof RuleOriginatedNotificationInfo) {
return ((RuleOriginatedNotificationInfo) request.getInfo()).getOriginatorEntityCustomerId();
} else {
return null;
}
} }
} }

19
application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java

@ -19,7 +19,9 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.TbEmail;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.mail.MailExecutorService;
@ -35,11 +37,26 @@ public class EmailNotificationChannel implements NotificationChannel<User, Email
@Override @Override
public ListenableFuture<Void> sendNotification(User recipient, EmailDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { public ListenableFuture<Void> sendNotification(User recipient, EmailDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
return executor.submit(() -> { return executor.submit(() -> {
mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), processedTemplate.getSubject(), processedTemplate.getBody()); mailService.send(recipient.getTenantId(), null, TbEmail.builder()
.to(recipient.getEmail())
.subject(processedTemplate.getSubject())
.body(processedTemplate.getBody())
.html(true)
.build());
return null; return null;
}); });
} }
@Override
public boolean check(TenantId tenantId) {
try {
mailService.testConnection(tenantId);
return true;
} catch (Exception e) {
return false;
}
}
@Override @Override
public NotificationDeliveryMethod getDeliveryMethod() { public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.EMAIL; return NotificationDeliveryMethod.EMAIL;

5
application/src/main/java/org/thingsboard/server/service/notification/channels/NotificationChannel.java

@ -16,15 +16,18 @@
package org.thingsboard.server.service.notification.channels; package org.thingsboard.server.service.notification.channels;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
public interface NotificationChannel<R extends NotificationRecipient, T extends DeliveryMethodNotificationTemplate> { public interface NotificationChannel<R extends NotificationRecipient, T extends DeliveryMethodNotificationTemplate> {
ListenableFuture<Void> sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx); ListenableFuture<Void> sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx);
boolean check(TenantId tenantId);
NotificationDeliveryMethod getDeliveryMethod(); NotificationDeliveryMethod getDeliveryMethod();
} }

10
application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java

@ -19,7 +19,10 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;
@ -31,6 +34,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService;
public class SlackNotificationChannel implements NotificationChannel<SlackConversation, SlackDeliveryMethodNotificationTemplate> { public class SlackNotificationChannel implements NotificationChannel<SlackConversation, SlackDeliveryMethodNotificationTemplate> {
private final SlackService slackService; private final SlackService slackService;
private final NotificationSettingsService notificationSettingsService;
private final ExternalCallExecutorService executor; private final ExternalCallExecutorService executor;
@Override @Override
@ -42,6 +46,12 @@ public class SlackNotificationChannel implements NotificationChannel<SlackConver
}); });
} }
@Override
public boolean check(TenantId tenantId) {
NotificationSettings notificationSettings = notificationSettingsService.findNotificationSettings(tenantId);
return notificationSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.SLACK);
}
@Override @Override
public NotificationDeliveryMethod getDeliveryMethod() { public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.SLACK; return NotificationDeliveryMethod.SLACK;

6
application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java

@ -22,6 +22,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.service.notification.NotificationProcessingContext;
@ -47,6 +48,11 @@ public class SmsNotificationChannel implements NotificationChannel<User, SmsDeli
}); });
} }
@Override
public boolean check(TenantId tenantId) {
return smsService.isConfigured(tenantId);
}
@Override @Override
public NotificationDeliveryMethod getDeliveryMethod() { public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.SMS; return NotificationDeliveryMethod.SMS;

78
application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java → application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessor.java

@ -32,16 +32,15 @@ import org.thingsboard.server.common.data.notification.NotificationRequestConfig
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.executors.NotificationExecutorService;
import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor; import org.thingsboard.server.service.notification.rule.trigger.NotificationRuleTriggerProcessor;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor; import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor;
@ -59,7 +58,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService { public class DefaultNotificationRuleProcessor implements NotificationRuleProcessor {
private final NotificationRuleService notificationRuleService; private final NotificationRuleService notificationRuleService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
@ -69,16 +68,17 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
private final Map<NotificationRuleTriggerType, NotificationRuleTriggerProcessor> triggerProcessors = new EnumMap<>(NotificationRuleTriggerType.class); private final Map<NotificationRuleTriggerType, NotificationRuleTriggerProcessor> triggerProcessors = new EnumMap<>(NotificationRuleTriggerType.class);
private final Map<String, NotificationRuleTriggerType> ruleEngineMsgTypeToTriggerType = new HashMap<>();
@Override @Override
public void process(TenantId tenantId, NotificationRuleTrigger trigger) { public void process(NotificationRuleTrigger trigger) {
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType( NotificationRuleTriggerType triggerType = trigger.getType();
trigger.getType().isTenantLevel() ? tenantId : TenantId.SYS_TENANT_ID, trigger.getType()); if (triggerType == null) return;
TenantId tenantId = triggerType.isTenantLevel() ? trigger.getTenantId() : TenantId.SYS_TENANT_ID;
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType);
for (NotificationRule rule : rules) { for (NotificationRule rule : rules) {
notificationExecutor.submit(() -> { notificationExecutor.submit(() -> {
try { try {
processNotificationRule(tenantId, rule, trigger); processNotificationRule(rule, trigger);
} catch (Throwable e) { } catch (Throwable e) {
log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), trigger, e); log.error("Failed to process notification rule {} for trigger type {} with trigger object {}", rule.getId(), rule.getTriggerType(), trigger, e);
} }
@ -86,24 +86,12 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
} }
} }
@Override private void processNotificationRule(NotificationRule rule, NotificationRuleTrigger trigger) {
public void process(TenantId tenantId, TbMsg ruleEngineMsg) {
NotificationRuleTriggerType triggerType = ruleEngineMsgTypeToTriggerType.get(ruleEngineMsg.getType());
if (triggerType == null) {
return;
}
process(tenantId, RuleEngineMsgTrigger.builder()
.msg(ruleEngineMsg)
.triggerType(triggerType)
.build());
}
private void processNotificationRule(TenantId tenantId, NotificationRule rule, NotificationRuleTrigger trigger) {
NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig(); NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig();
log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType()); log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType());
if (matchesClearRule(trigger, triggerConfig)) { if (matchesClearRule(trigger, triggerConfig)) {
List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, rule.getId(), trigger.getOriginatorEntityId()); List<NotificationRequest> notificationRequests = findAlreadySentNotificationRequests(rule, trigger);
if (notificationRequests.isEmpty()) { if (notificationRequests.isEmpty()) {
return; return;
} }
@ -113,11 +101,11 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
.flatMap(notificationRequest -> notificationRequest.getTargets().stream()) .flatMap(notificationRequest -> notificationRequest.getTargets().stream())
.distinct().collect(Collectors.toList()); .distinct().collect(Collectors.toList());
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig); NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
submitNotificationRequest(tenantId, targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0); submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0);
notificationRequests.forEach(notificationRequest -> { notificationRequests.forEach(notificationRequest -> {
if (notificationRequest.isScheduled()) { if (notificationRequest.isScheduled()) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequest.getId()); notificationCenter.deleteNotificationRequest(rule.getTenantId(), notificationRequest.getId());
} }
}); });
return; return;
@ -126,31 +114,23 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
if (matchesFilter(trigger, triggerConfig)) { if (matchesFilter(trigger, triggerConfig)) {
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig); NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> { rule.getRecipientsConfig().getTargetsTable().forEach((delay, targets) -> {
submitNotificationRequest(tenantId, targets, rule, trigger.getOriginatorEntityId(), notificationInfo, delay); submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, delay);
}); });
} }
} }
private boolean matchesFilter(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) { private List<NotificationRequest> findAlreadySentNotificationRequests(NotificationRule rule, NotificationRuleTrigger trigger) {
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesFilter(trigger, triggerConfig); return notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), trigger.getOriginatorEntityId());
} }
private boolean matchesClearRule(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) { private void submitNotificationRequest(List<UUID> targets, NotificationRule rule,
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesClearRule(trigger, triggerConfig);
}
private NotificationInfo constructNotificationInfo(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).constructNotificationInfo(trigger, triggerConfig);
}
private void submitNotificationRequest(TenantId tenantId, List<UUID> targets, NotificationRule rule,
EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) { EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig(); NotificationRequestConfig config = new NotificationRequestConfig();
if (delayInSec > 0) { if (delayInSec > 0) {
config.setSendingDelayInSec(delayInSec); config.setSendingDelayInSec(delayInSec);
} }
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId) .tenantId(rule.getTenantId())
.targets(targets) .targets(targets)
.templateId(rule.getTemplateId()) .templateId(rule.getTemplateId())
.additionalConfig(config) .additionalConfig(config)
@ -161,13 +141,25 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
notificationExecutor.submit(() -> { notificationExecutor.submit(() -> {
try { try {
log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delayInSec, targets); log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delayInSec, targets);
notificationCenter.processNotificationRequest(tenantId, notificationRequest); notificationCenter.processNotificationRequest(rule.getTenantId(), notificationRequest, null);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to process notification request for rule {}", rule.getId(), e); log.error("Failed to process notification request for tenant {} for rule {}", rule.getTenantId(), rule.getId(), e);
} }
}); });
} }
private boolean matchesFilter(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesFilter(trigger, triggerConfig);
}
private boolean matchesClearRule(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesClearRule(trigger, triggerConfig);
}
private NotificationInfo constructNotificationInfo(NotificationRuleTrigger trigger, NotificationRuleTriggerConfig triggerConfig) {
return triggerProcessors.get(triggerConfig.getTriggerType()).constructNotificationInfo(trigger);
}
@EventListener(ComponentLifecycleMsg.class) @EventListener(ComponentLifecycleMsg.class)
public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) { public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) {
if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED || if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED ||
@ -187,6 +179,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
@Autowired @Autowired
public void setTriggerProcessors(Collection<NotificationRuleTriggerProcessor> processors) { public void setTriggerProcessors(Collection<NotificationRuleTriggerProcessor> processors) {
Map<String, NotificationRuleTriggerType> ruleEngineMsgTypeToTriggerType = new HashMap<>();
processors.forEach(processor -> { processors.forEach(processor -> {
triggerProcessors.put(processor.getTriggerType(), processor); triggerProcessors.put(processor.getTriggerType(), processor);
if (processor instanceof RuleEngineMsgNotificationRuleTriggerProcessor) { if (processor instanceof RuleEngineMsgNotificationRuleTriggerProcessor) {
@ -196,6 +189,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
}); });
} }
}); });
RuleEngineMsgTrigger.msgTypeToTriggerType = ruleEngineMsgTypeToTriggerType;
} }
} }

10
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java

@ -23,11 +23,11 @@ import org.thingsboard.server.common.data.alarm.AlarmAssignee;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmAssignmentNotificationInfo; import org.thingsboard.server.common.data.notification.info.AlarmAssignmentNotificationInfo;
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.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig.Action; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignmentNotificationRuleTriggerConfig.Action;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import java.util.Set; import java.util.Set;
@ -49,7 +49,7 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
AlarmInfo alarmInfo = JacksonUtil.fromString(trigger.getMsg().getData(), AlarmInfo.class); AlarmInfo alarmInfo = JacksonUtil.fromString(trigger.getMsg().getData(), AlarmInfo.class);
AlarmAssignee assignee = alarmInfo.getAssignee(); AlarmAssignee assignee = alarmInfo.getAssignee();
return AlarmAssignmentNotificationInfo.builder() return AlarmAssignmentNotificationInfo.builder()
@ -58,7 +58,9 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
.assigneeLastName(assignee != null ? assignee.getLastName() : null) .assigneeLastName(assignee != null ? assignee.getLastName() : null)
.assigneeEmail(assignee != null ? assignee.getEmail() : null) .assigneeEmail(assignee != null ? assignee.getEmail() : null)
.assigneeId(assignee != null ? assignee.getId() : null) .assigneeId(assignee != null ? assignee.getId() : null)
.userName(trigger.getMsg().getMetaData().getValue("userName")) .userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
.alarmId(alarmInfo.getUuidId()) .alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType()) .alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator()) .alarmOriginator(alarmInfo.getOriginator())

10
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java

@ -24,11 +24,11 @@ import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo; 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.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentNotificationRuleTriggerConfig; 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.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import java.util.Set; import java.util.Set;
@ -59,14 +59,16 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, AlarmCommentNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg(); TbMsg msg = trigger.getMsg();
AlarmComment comment = JacksonUtil.fromString(msg.getMetaData().getValue("comment"), AlarmComment.class); AlarmComment comment = JacksonUtil.fromString(msg.getMetaData().getValue("comment"), AlarmComment.class);
AlarmInfo alarmInfo = JacksonUtil.fromString(msg.getData(), AlarmInfo.class); AlarmInfo alarmInfo = JacksonUtil.fromString(msg.getData(), AlarmInfo.class);
return AlarmCommentNotificationInfo.builder() return AlarmCommentNotificationInfo.builder()
.comment(comment.getComment().get("text").asText()) .comment(comment.getComment().get("text").asText())
.action(msg.getType().equals(DataConstants.COMMENT_CREATED) ? "added" : "updated") .action(msg.getType().equals(DataConstants.COMMENT_CREATED) ? "added" : "updated")
.userName(msg.getMetaData().getValue("userName")) .userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
.alarmId(alarmInfo.getUuidId()) .alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType()) .alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator()) .alarmOriginator(alarmInfo.getOriginator())

8
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java

@ -20,13 +20,13 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; 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.rule.trigger.AlarmNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule; 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.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.notification.trigger.AlarmTrigger; import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger;
import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty; import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@ -93,7 +93,7 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
} }
@Override @Override
public NotificationInfo constructNotificationInfo(AlarmTrigger trigger, AlarmNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(AlarmTrigger trigger) {
AlarmApiCallResult alarmUpdate = trigger.getAlarmUpdate(); AlarmApiCallResult alarmUpdate = trigger.getAlarmUpdate();
AlarmInfo alarmInfo = alarmUpdate.getAlarm(); AlarmInfo alarmInfo = alarmUpdate.getAlarm();
return AlarmNotificationInfo.builder() return AlarmNotificationInfo.builder()

59
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2023 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.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.ApiUsageLimitNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.ApiUsageLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger;
import org.thingsboard.server.dao.tenant.TenantService;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service
@RequiredArgsConstructor
public class ApiUsageLimitTriggerProcessor implements NotificationRuleTriggerProcessor<ApiUsageLimitTrigger, ApiUsageLimitNotificationRuleTriggerConfig> {
private final TenantService tenantService;
@Override
public boolean matchesFilter(ApiUsageLimitTrigger trigger, ApiUsageLimitNotificationRuleTriggerConfig triggerConfig) {
return (isEmpty(triggerConfig.getApiFeatures()) || triggerConfig.getApiFeatures().contains(trigger.getState().getApiFeature())) &&
(isEmpty(triggerConfig.getNotifyOn()) || triggerConfig.getNotifyOn().contains(trigger.getStatus()));
}
@Override
public RuleOriginatedNotificationInfo constructNotificationInfo(ApiUsageLimitTrigger trigger) {
return ApiUsageLimitNotificationInfo.builder()
.feature(trigger.getState().getApiFeature())
.recordKey(trigger.getState().getKey())
.status(trigger.getStatus())
.limit(trigger.getState().getThresholdAsString())
.currentValue(trigger.getState().getValueAsString())
.tenantId(trigger.getTenantId())
.tenantName(tenantService.findTenantById(trigger.getTenantId()).getName())
.build();
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.API_USAGE_LIMIT;
}
}

26
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceInactivityTriggerProcessor.java → application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/DeviceActivityTriggerProcessor.java

@ -22,24 +22,29 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.DeviceInactivityNotificationInfo; import org.thingsboard.server.common.data.notification.info.DeviceActivityNotificationInfo;
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.rule.trigger.DeviceInactivityNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import java.util.Set; import java.util.Set;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<DeviceInactivityNotificationRuleTriggerConfig> { public class DeviceActivityTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<DeviceActivityNotificationRuleTriggerConfig> {
private final TbDeviceProfileCache deviceProfileCache; private final TbDeviceProfileCache deviceProfileCache;
@Override @Override
public boolean matchesFilter(RuleEngineMsgTrigger trigger, DeviceInactivityNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(RuleEngineMsgTrigger trigger, DeviceActivityNotificationRuleTriggerConfig triggerConfig) {
DeviceEvent event = trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? DeviceEvent.ACTIVE : DeviceEvent.INACTIVE;
if (!triggerConfig.getNotifyOn().contains(event)) {
return false;
}
DeviceId deviceId = (DeviceId) trigger.getMsg().getOriginator(); DeviceId deviceId = (DeviceId) trigger.getMsg().getOriginator();
if (CollectionUtils.isNotEmpty(triggerConfig.getDevices())) { if (CollectionUtils.isNotEmpty(triggerConfig.getDevices())) {
return triggerConfig.getDevices().contains(deviceId.getId()); return triggerConfig.getDevices().contains(deviceId.getId());
@ -52,9 +57,10 @@ public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificati
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, DeviceInactivityNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg(); TbMsg msg = trigger.getMsg();
return DeviceInactivityNotificationInfo.builder() return DeviceActivityNotificationInfo.builder()
.eventType(trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? "active" : "inactive")
.deviceId(msg.getOriginator().getId()) .deviceId(msg.getOriginator().getId())
.deviceName(msg.getMetaData().getValue("deviceName")) .deviceName(msg.getMetaData().getValue("deviceName"))
.deviceType(msg.getMetaData().getValue("deviceType")) .deviceType(msg.getMetaData().getValue("deviceType"))
@ -65,12 +71,12 @@ public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificati
@Override @Override
public NotificationRuleTriggerType getTriggerType() { public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.DEVICE_INACTIVITY; return NotificationRuleTriggerType.DEVICE_ACTIVITY;
} }
@Override @Override
public Set<String> getSupportedMsgTypes() { public Set<String> getSupportedMsgTypes() {
return Set.of(DataConstants.INACTIVITY_EVENT); return Set.of(DataConstants.ACTIVITY_EVENT, DataConstants.INACTIVITY_EVENT);
} }
} }

6
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntitiesLimitTriggerProcessor.java

@ -18,10 +18,10 @@ package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotificationInfo; import org.thingsboard.server.common.data.notification.info.EntitiesLimitNotificationInfo;
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.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.notification.trigger.EntitiesLimitTrigger; import org.thingsboard.server.common.msg.notification.trigger.EntitiesLimitTrigger;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty; import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@ -41,7 +41,7 @@ public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerPro
} }
@Override @Override
public NotificationInfo constructNotificationInfo(EntitiesLimitTrigger trigger, EntitiesLimitNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(EntitiesLimitTrigger trigger) {
return EntitiesLimitNotificationInfo.builder() return EntitiesLimitNotificationInfo.builder()
.entityType(trigger.getEntityType()) .entityType(trigger.getEntityType())
.currentCount(trigger.getCurrentCount()) .currentCount(trigger.getCurrentCount())

16
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java

@ -20,16 +20,18 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo; import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo;
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.rule.trigger.EntityActionNotificationRuleTriggerConfig; 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.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service @Service
public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<EntityActionNotificationRuleTriggerConfig> { public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<EntityActionNotificationRuleTriggerConfig> {
@ -51,11 +53,11 @@ public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRu
} else { } else {
return false; return false;
} }
return triggerConfig.getEntityType() == null || getEntityType(trigger.getMsg()) == triggerConfig.getEntityType(); return isEmpty(triggerConfig.getEntityTypes()) || triggerConfig.getEntityTypes().contains(getEntityType(trigger.getMsg()));
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg(); TbMsg msg = trigger.getMsg();
String msgType = msg.getType(); String msgType = msg.getType();
ActionType actionType = msgType.equals(DataConstants.ENTITY_CREATED) ? ActionType.ADDED : ActionType actionType = msgType.equals(DataConstants.ENTITY_CREATED) ? ActionType.ADDED :
@ -65,8 +67,10 @@ public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRu
.entityId(msg.getOriginator()) .entityId(msg.getOriginator())
.entityName(msg.getMetaData().getValue("entityName")) .entityName(msg.getMetaData().getValue("entityName"))
.actionType(actionType) .actionType(actionType)
.originatorUserId(UUID.fromString(msg.getMetaData().getValue("userId"))) .userId(UUID.fromString(msg.getMetaData().getValue("userId")))
.originatorUserName(msg.getMetaData().getValue("userName")) .userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
.entityCustomerId(msg.getCustomerId()) .entityCustomerId(msg.getCustomerId())
.build(); .build();
} }

6
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NewPlatformVersionTriggerProcessor.java

@ -21,9 +21,9 @@ import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo; import org.thingsboard.server.common.data.notification.info.NewPlatformVersionNotificationInfo;
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.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionTrigger; import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
@ -44,7 +44,7 @@ public class NewPlatformVersionTriggerProcessor implements NotificationRuleTrigg
} }
@Override @Override
public NotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger) {
return NewPlatformVersionNotificationInfo.builder() return NewPlatformVersionNotificationInfo.builder()
.message(JacksonUtil.convertValue(trigger.getMessage(), new TypeReference<>() {})) .message(JacksonUtil.convertValue(trigger.getMessage(), new TypeReference<>() {}))
.build(); .build();

6
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/NotificationRuleTriggerProcessor.java

@ -15,8 +15,8 @@
*/ */
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
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.rule.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@ -28,7 +28,7 @@ public interface NotificationRuleTriggerProcessor<T extends NotificationRuleTrig
return false; return false;
} }
NotificationInfo constructNotificationInfo(T trigger, C triggerConfig); RuleOriginatedNotificationInfo constructNotificationInfo(T trigger);
NotificationRuleTriggerType getTriggerType(); NotificationRuleTriggerType getTriggerType();

15
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineComponentLifecycleEventTriggerProcessor.java

@ -15,25 +15,31 @@
*/ */
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
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.info.RuleEngineComponentLifecycleEventNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; 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.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.notification.trigger.RuleEngineComponentLifecycleEventTrigger; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineComponentLifecycleEventTrigger;
import org.thingsboard.server.queue.discovery.PartitionService;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Set; import java.util.Set;
@Service @Service
@RequiredArgsConstructor
public class RuleEngineComponentLifecycleEventTriggerProcessor implements NotificationRuleTriggerProcessor<RuleEngineComponentLifecycleEventTrigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig> { public class RuleEngineComponentLifecycleEventTriggerProcessor implements NotificationRuleTriggerProcessor<RuleEngineComponentLifecycleEventTrigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig> {
private final PartitionService partitionService;
@Override @Override
public boolean matchesFilter(RuleEngineComponentLifecycleEventTrigger trigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(RuleEngineComponentLifecycleEventTrigger trigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) {
if (CollectionUtils.isNotEmpty(triggerConfig.getRuleChains())) { if (CollectionUtils.isNotEmpty(triggerConfig.getRuleChains())) {
@ -41,6 +47,9 @@ public class RuleEngineComponentLifecycleEventTriggerProcessor implements Notifi
return false; return false;
} }
} }
if (!partitionService.resolve(ServiceType.TB_RULE_ENGINE, trigger.getTenantId(), trigger.getComponentId()).isMyPartition()) {
return false;
}
EntityType componentType = trigger.getComponentId().getEntityType(); EntityType componentType = trigger.getComponentId().getEntityType();
Set<ComponentLifecycleEvent> trackedEvents; Set<ComponentLifecycleEvent> trackedEvents;
@ -68,7 +77,7 @@ public class RuleEngineComponentLifecycleEventTriggerProcessor implements Notifi
} }
@Override @Override
public NotificationInfo constructNotificationInfo(RuleEngineComponentLifecycleEventTrigger trigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) { public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineComponentLifecycleEventTrigger trigger) {
return RuleEngineComponentLifecycleEventNotificationInfo.builder() return RuleEngineComponentLifecycleEventNotificationInfo.builder()
.ruleChainId(trigger.getRuleChainId()) .ruleChainId(trigger.getRuleChainId())
.ruleChainName(trigger.getRuleChainName()) .ruleChainName(trigger.getRuleChainName())

2
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/RuleEngineMsgNotificationRuleTriggerProcessor.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.dao.notification.trigger.RuleEngineMsgTrigger; import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import java.util.Set; import java.util.Set;

10
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -35,10 +35,12 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
@ -129,6 +131,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
private final OtaPackageStateService firmwareStateService; private final OtaPackageStateService firmwareStateService;
private final GitVersionControlQueueService vcQueueService; private final GitVersionControlQueueService vcQueueService;
private final NotificationSchedulerService notificationSchedulerService; private final NotificationSchedulerService notificationSchedulerService;
private final NotificationRuleProcessor notificationRuleProcessor;
private final TbCoreConsumerStats stats; private final TbCoreConsumerStats stats;
protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer; protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer;
private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer; private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer;
@ -156,7 +159,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
PartitionService partitionService, PartitionService partitionService,
ApplicationEventPublisher eventPublisher, ApplicationEventPublisher eventPublisher,
Optional<JwtSettingsService> jwtSettingsService, Optional<JwtSettingsService> jwtSettingsService,
NotificationSchedulerService notificationSchedulerService) { NotificationSchedulerService notificationSchedulerService,
NotificationRuleProcessor notificationRuleProcessor) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
@ -171,6 +175,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
this.firmwareStateService = firmwareStateService; this.firmwareStateService = firmwareStateService;
this.vcQueueService = vcQueueService; this.vcQueueService = vcQueueService;
this.notificationSchedulerService = notificationSchedulerService; this.notificationSchedulerService = notificationSchedulerService;
this.notificationRuleProcessor = notificationRuleProcessor;
} }
@PostConstruct @PostConstruct
@ -269,6 +274,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg(); TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg();
log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg()); log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg());
forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback); forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback);
} else if (toCoreMsg.hasNotificationRuleProcessorMsg()) {
Optional<NotificationRuleTrigger> notificationRuleTrigger = encodingService.decode(toCoreMsg.getNotificationRuleProcessorMsg().getTrigger().toByteArray());
notificationRuleTrigger.ifPresent(notificationRuleProcessor::process);
} }
} catch (Throwable e) { } catch (Throwable e) {
log.warn("[{}] Failed to process message: {}", id, msg, e); log.warn("[{}] Failed to process message: {}", id, msg, e);

19
application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java

@ -44,6 +44,8 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultSlackService implements SlackService { public class DefaultSlackService implements SlackService {
@ -80,7 +82,14 @@ public class DefaultSlackService implements SlackService {
.map(user -> { .map(user -> {
SlackConversation conversation = new SlackConversation(); SlackConversation conversation = new SlackConversation();
conversation.setId(user.getId()); conversation.setId(user.getId());
conversation.setName(String.format("@%s (%s)", user.getName(), user.getRealName())); conversation.setShortName(user.getName());
conversation.setWholeName(user.getProfile() != null ? user.getProfile().getRealNameNormalized() : user.getRealName());
conversation.setEmail(user.getProfile() != null ? user.getProfile().getEmail() : null);
String title = "@" + conversation.getShortName();
if (isNotEmpty(conversation.getWholeName()) && !conversation.getWholeName().equals(conversation.getShortName())) {
title += " (" + conversation.getWholeName() + ")";
}
conversation.setTitle(title);
return conversation; return conversation;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -99,7 +108,9 @@ public class DefaultSlackService implements SlackService {
.map(channel -> { .map(channel -> {
SlackConversation conversation = new SlackConversation(); SlackConversation conversation = new SlackConversation();
conversation.setId(channel.getId()); conversation.setId(channel.getId());
conversation.setName("#" + channel.getName()); conversation.setShortName(channel.getName());
conversation.setWholeName(channel.getNameNormalized());
conversation.setTitle("#" + channel.getName());
return conversation; return conversation;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -111,7 +122,7 @@ public class DefaultSlackService implements SlackService {
public SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern) { public SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern) {
List<SlackConversation> conversations = listConversations(tenantId, token, conversationType); List<SlackConversation> conversations = listConversations(tenantId, token, conversationType);
return conversations.stream() return conversations.stream()
.filter(conversation -> StringUtils.containsIgnoreCase(conversation.getName(), namePattern)) .filter(conversation -> StringUtils.containsIgnoreCase(conversation.getTitle(), namePattern))
.findFirst().orElse(null); .findFirst().orElse(null);
} }
@ -144,7 +155,7 @@ public class DefaultSlackService implements SlackService {
String neededScope = response.getNeeded(); String neededScope = response.getNeeded();
error = "bot token scope '" + neededScope + "' is needed"; error = "bot token scope '" + neededScope + "' is needed";
} }
throw new RuntimeException("Failed to send message via Slack: " + error); throw new RuntimeException("Slack API error: " + error);
} }
return response; return response;

35
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -316,20 +316,11 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
Set<TbSubscription> subscriptions = subscriptionsByEntityId.get(recipientId); Set<TbSubscription> subscriptions = subscriptionsByEntityId.get(recipientId);
if (subscriptions != null) { if (subscriptions != null) {
NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationUpdate); NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationUpdate);
log.trace("Handling notificationUpdate for user {}: {}", recipientId, notificationUpdate);
subscriptions.stream() subscriptions.stream()
.filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS .filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS
|| subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT) || subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT)
.forEach(subscription -> { .forEach(subscription -> onNotificationsSubUpdate(subscriptionUpdate, subscription));
if (serviceId.equals(subscription.getServiceId())) {
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(),
subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY);
} else {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId());
ToCoreNotificationMsg updateProto = TbSubscriptionUtils.notificationsSubUpdateToProto(subscription, subscriptionUpdate);
TbProtoQueueMsg<ToCoreNotificationMsg> queueMsg = new TbProtoQueueMsg<>(subscription.getEntityId().getId(), updateProto);
toCoreNotificationsProducer.send(tpi, queueMsg, null);
}
});
} }
callback.onSuccess(); callback.onSuccess();
} }
@ -341,21 +332,37 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
if (entityId.getEntityType() != EntityType.USER) { if (entityId.getEntityType() != EntityType.USER) {
return; return;
} }
log.trace("Handling notificationRequestUpdate for user {}: {}", entityId, notificationRequestUpdate);
subscriptions.forEach(subscription -> { subscriptions.forEach(subscription -> {
if (subscription.getType() != TbSubscriptionType.NOTIFICATIONS && if (subscription.getType() != TbSubscriptionType.NOTIFICATIONS &&
subscription.getType() != TbSubscriptionType.NOTIFICATIONS_COUNT) { subscription.getType() != TbSubscriptionType.NOTIFICATIONS_COUNT) {
return; return;
} }
if (!subscription.getTenantId().equals(tenantId) || !subscription.getServiceId().equals(serviceId)) { if (!subscription.getTenantId().equals(tenantId)) {
return; return;
} }
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(), onNotificationsSubUpdate(subscriptionUpdate, subscription);
subscriptionUpdate, TbCallback.EMPTY);
}); });
}); });
callback.onSuccess(); callback.onSuccess();
} }
private void onNotificationsSubUpdate(NotificationsSubscriptionUpdate subscriptionUpdate, TbSubscription subscription) {
if (serviceId.equals(subscription.getServiceId())) {
log.trace("[{}][{}][{}] Subscription session is managed by current service, forwarding to localSubscriptionService (update: {})",
subscription.getServiceId(), subscription.getEntityId(), subscription.getSessionId(), subscriptionUpdate);
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(),
subscription.getSubscriptionId(), subscriptionUpdate, TbCallback.EMPTY);
} else {
log.trace("[{}][{}][{}] Subscription session is not managed by current service (update: {})",
subscription.getServiceId(), subscription.getEntityId(), subscription.getSessionId(), subscriptionUpdate);
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, subscription.getServiceId());
ToCoreNotificationMsg updateProto = TbSubscriptionUtils.notificationsSubUpdateToProto(subscription, subscriptionUpdate);
TbProtoQueueMsg<ToCoreNotificationMsg> queueMsg = new TbProtoQueueMsg<>(subscription.getEntityId().getId(), updateProto);
toCoreNotificationsProducer.send(tpi, queueMsg, null);
}
}
@Override @Override
public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, TbCallback callback) { public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId, onLocalTelemetrySubUpdate(entityId,

3
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -94,8 +94,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private static final int DEFAULT_LIMIT = 100; private static final int DEFAULT_LIMIT = 100;
private final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>(); private final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
@Autowired @Autowired @Lazy
@Lazy
private WebSocketService wsService; private WebSocketService wsService;
@Autowired @Autowired

14
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -26,6 +26,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.alarm.AlarmCommentType;
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest;
@ -45,15 +46,14 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.dao.notification.trigger.AlarmTrigger;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection; import java.util.Collection;
@ -70,7 +70,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
private final TbAlarmCommentService alarmCommentService; private final TbAlarmCommentService alarmCommentService;
private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService; private final TbApiUsageStateService apiUsageStateService;
private final NotificationRuleProcessingService notificationRuleProcessingService; private final NotificationRuleProcessor notificationRuleProcessor;
@Override @Override
protected String getExecutorPrefix() { protected String getExecutorPrefix() {
@ -235,7 +235,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
}); });
} }
notificationRuleProcessingService.process(tenantId, AlarmTrigger.builder() notificationRuleProcessor.process(AlarmTrigger.builder()
.tenantId(tenantId)
.alarmUpdate(result) .alarmUpdate(result)
.build()); .build());
}); });
@ -252,7 +253,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm);
}); });
} }
notificationRuleProcessingService.process(tenantId, AlarmTrigger.builder() notificationRuleProcessor.process(AlarmTrigger.builder()
.tenantId(tenantId)
.alarmUpdate(result) .alarmUpdate(result)
.build()); .build());
}); });

9
application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java

@ -26,9 +26,8 @@ import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionTrigger; import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -62,7 +61,7 @@ public class DefaultUpdateService implements UpdateService {
private BuildProperties buildProperties; private BuildProperties buildProperties;
@Autowired @Autowired
private NotificationRuleProcessingService notificationRuleProcessingService; private NotificationRuleProcessor notificationRuleProcessor;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service")); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service"));
@ -132,7 +131,7 @@ public class DefaultUpdateService implements UpdateService {
UpdateMessage prevUpdateMessage = updateMessage; UpdateMessage prevUpdateMessage = updateMessage;
updateMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class); updateMessage = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class);
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) { if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessingService.process(TenantId.SYS_TENANT_ID, NewPlatformVersionTrigger.builder() notificationRuleProcessor.process(NewPlatformVersionTrigger.builder()
.message(updateMessage) .message(updateMessage)
.build()); .build());
} }

26
application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java

@ -120,17 +120,21 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
/* Notifications subscription update handling */ /* Notifications subscription update handling */
private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
if (subscriptionUpdate.getNotificationUpdate() != null) { try {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate()); if (subscriptionUpdate.getNotificationUpdate() != null) {
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) { handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate()); } else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
}
} catch (Exception e) {
log.error("[{}, subId: {}] Failed to handle update for notifications subscription: {}", subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, e);
} }
} }
private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) { private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) {
log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification(); Notification notification = update.getNotification();
UUID notificationId = update.getNotificationId(); UUID notificationId = notification != null ? notification.getUuidId() : update.getNotificationId();
if (update.isCreated()) { if (update.isCreated()) {
subscription.getLatestUnreadNotifications().put(notificationId, notification); subscription.getLatestUnreadNotifications().put(notificationId, notification);
subscription.getTotalUnreadCounter().incrementAndGet(); subscription.getTotalUnreadCounter().incrementAndGet();
@ -175,10 +179,14 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
/* Notifications count subscription update handling */ /* Notifications count subscription update handling */
private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) { private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
if (subscriptionUpdate.getNotificationUpdate() != null) { try {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate()); if (subscriptionUpdate.getNotificationUpdate() != null) {
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) { handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate()); } else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
}
} catch (Exception e) {
log.error("[{}, subId: {}] Failed to handle update for notifications count subscription: {}", subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, e);
} }
} }

10
application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java

@ -15,12 +15,10 @@
*/ */
package org.thingsboard.server.service.ws.notification.sub; package org.thingsboard.server.service.ws.notification.sub;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.NotificationStatus;
@ -32,7 +30,7 @@ import java.util.UUID;
@Builder @Builder
public class NotificationUpdate { public class NotificationUpdate {
private NotificationId notificationId; private UUID notificationId;
private boolean created; private boolean created;
private Notification notification; private Notification notification;
@ -43,10 +41,4 @@ public class NotificationUpdate {
private boolean deleted; private boolean deleted;
@JsonIgnore
public UUID getNotificationId() {
return notificationId != null ? notificationId.getId() :
notification != null ? notification.getUuidId() : null;
}
} }

4
application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.entitiy.alarm; package org.thingsboard.server.service.entitiy.alarm;
import com.google.common.util.concurrent.Futures;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -30,8 +29,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;

6
application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java

@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.notification.NotificationRequestConfig
import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
@ -44,8 +43,8 @@ import org.thingsboard.server.common.data.notification.template.DeliveryMethodNo
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; 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.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
@ -110,12 +109,9 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
protected NotificationRequest submitNotificationRequest(List<NotificationTargetId> targets, NotificationTemplateId notificationTemplateId, int delayInSec) { protected NotificationRequest submitNotificationRequest(List<NotificationTargetId> targets, NotificationTemplateId notificationTemplateId, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig(); NotificationRequestConfig config = new NotificationRequestConfig();
config.setSendingDelayInSec(delayInSec); config.setSendingDelayInSec(delayInSec);
UserOriginatedNotificationInfo notificationInfo = new UserOriginatedNotificationInfo();
notificationInfo.setDescription("My description");
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.targets(targets.stream().map(UUIDBased::getId).collect(Collectors.toList())) .targets(targets.stream().map(UUIDBased::getId).collect(Collectors.toList()))
.templateId(notificationTemplateId) .templateId(notificationTemplateId)
.info(notificationInfo)
.additionalConfig(config) .additionalConfig(config)
.build(); .build();
return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class);

81
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -15,18 +15,15 @@
*/ */
package org.thingsboard.server.service.notification; package org.thingsboard.server.service.notification;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.assertj.core.data.Offset; import org.assertj.core.data.Offset;
import org.java_websocket.client.WebSocketClient; import org.java_websocket.client.WebSocketClient;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
@ -36,11 +33,9 @@ import org.thingsboard.server.common.data.notification.NotificationRequestPrevie
import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
@ -50,11 +45,9 @@ import org.thingsboard.server.common.data.notification.template.DeliveryMethodNo
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; 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.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationDao; import org.thingsboard.server.dao.notification.NotificationDao;
@ -67,8 +60,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.type; import static org.assertj.core.api.InstanceOfAssertFactories.type;
@ -341,8 +332,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
target1Config.setUsersFilter(userListFilter); target1Config.setUsersFilter(userListFilter);
target1.setConfiguration(target1Config); target1.setConfiguration(target1Config);
target1 = saveNotificationTarget(target1); target1 = saveNotificationTarget(target1);
List<UserId> recipients = new ArrayList<>(); List<String> recipients = new ArrayList<>();
recipients.add(tenantAdminUserId); recipients.add(TENANT_ADMIN_EMAIL);
createDifferentCustomer(); createDifferentCustomer();
loginTenantAdmin(); loginTenantAdmin();
@ -354,7 +345,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
customerUser.setCustomerId(differentCustomerId); customerUser.setCustomerId(differentCustomerId);
customerUser.setEmail("other-customer-" + i + "@thingsboard.org"); customerUser.setEmail("other-customer-" + i + "@thingsboard.org");
customerUser = createUser(customerUser, "12345678"); customerUser = createUser(customerUser, "12345678");
recipients.add(customerUser.getId()); recipients.add(customerUser.getEmail());
} }
NotificationTarget target2 = new NotificationTarget(); NotificationTarget target2 = new NotificationTarget();
target2.setName("Other customer users"); target2.setName("Other customer users");
@ -378,7 +369,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate(); WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
webNotificationTemplate.setEnabled(true); webNotificationTemplate.setEnabled(true);
webNotificationTemplate.setBody("Message for WEB: ${recipientEmail}"); webNotificationTemplate.setBody("Message for WEB: ${recipientEmail} ${unknownParam}");
webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}"); webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}");
templates.put(NotificationDeliveryMethod.WEB, webNotificationTemplate); templates.put(NotificationDeliveryMethod.WEB, webNotificationTemplate);
@ -410,14 +401,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(preview.getRecipientsCountByTarget().get(target1.getName())).isEqualTo(1); assertThat(preview.getRecipientsCountByTarget().get(target1.getName())).isEqualTo(1);
assertThat(preview.getRecipientsCountByTarget().get(target2.getName())).isEqualTo(customerUsersCount); assertThat(preview.getRecipientsCountByTarget().get(target2.getName())).isEqualTo(customerUsersCount);
assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount); assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount);
assertThat(preview.getRecipientsPreview()).extracting(User::getId).containsAll(recipients); assertThat(preview.getRecipientsPreview()).containsAll(recipients);
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = preview.getProcessedTemplates(); Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = preview.getProcessedTemplates();
assertThat(processedTemplates.get(NotificationDeliveryMethod.WEB)).asInstanceOf(type(WebDeliveryMethodNotificationTemplate.class)) assertThat(processedTemplates.get(NotificationDeliveryMethod.WEB)).asInstanceOf(type(WebDeliveryMethodNotificationTemplate.class))
.satisfies(template -> { .satisfies(template -> {
assertThat(template.getBody()) assertThat(template.getBody())
.startsWith("Message for WEB") .startsWith("Message for WEB")
.endsWith(requestorEmail); .endsWith(requestorEmail + " ${unknownParam}");
assertThat(template.getSubject()) assertThat(template.getSubject())
.startsWith("Subject for WEB") .startsWith("Subject for WEB")
.endsWith(requestorEmail); .endsWith(requestorEmail);
@ -440,7 +431,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class)) assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class))
.satisfies(template -> { .satisfies(template -> {
assertThat(template.getBody()) assertThat(template.getBody())
.isEqualTo("Message for SLACK: "); // ${recipientEmail} should be removed .isEqualTo("Message for SLACK: ${recipientEmail}"); // ${recipientEmail} should not be processed
}); });
} }
@ -477,53 +468,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
} }
@Test @Test
@Ignore
public void testNotificationsForALotOfUsers() throws Exception {
int usersCount = 5000;
List<User> users = new ArrayList<>();
for (int i = 1; i <= usersCount; i++) {
User user = new User();
user.setTenantId(tenantId);
user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail("test-user-" + i + "@thingsboard.org");
user = doPost("/api/user", user, User.class);
users.add(user);
}
NotificationTarget notificationTarget = new NotificationTarget();
notificationTarget.setTenantId(tenantId);
notificationTarget.setName("All my users");
PlatformUsersNotificationTargetConfig config = new PlatformUsersNotificationTargetConfig();
AllUsersFilter filter = new AllUsersFilter();
config.setUsersFilter(filter);
notificationTarget.setConfiguration(config);
notificationTarget = saveNotificationTarget(notificationTarget);
NotificationTargetId notificationTargetId = notificationTarget.getId();
ListenableFuture<NotificationRequest> request = executor.submit(() -> {
return submitNotificationRequest(notificationTargetId, "Hello, ${recipientEmail}", 0, NotificationDeliveryMethod.WEB);
});
await().atMost(10, TimeUnit.SECONDS).until(request::isDone);
NotificationRequest notificationRequest = request.get();
await().atMost(5, TimeUnit.SECONDS)
.pollInterval(200, TimeUnit.MILLISECONDS)
.until(() -> {
PageData<Notification> sentNotifications = notificationDao.findByRequestId(tenantId, notificationRequest.getId(), new PageLink(1));
return sentNotifications.getTotalElements() >= usersCount;
});
PageData<Notification> sentNotifications = notificationDao.findByRequestId(tenantId, notificationRequest.getId(), new PageLink(Integer.MAX_VALUE));
assertThat(sentNotifications.getData()).extracting(Notification::getRecipientId)
.containsAll(users.stream().map(User::getId).collect(Collectors.toSet()));
NotificationRequestStats stats = getStats(notificationRequest.getId());
assertThat(stats.getSent().values().stream().mapToInt(AtomicInteger::get).sum()).isGreaterThanOrEqualTo(usersCount);
}
@Test
@Ignore
public void testSlackNotifications() throws Exception { public void testSlackNotifications() throws Exception {
NotificationSettings settings = new NotificationSettings(); NotificationSettings settings = new NotificationSettings();
SlackNotificationDeliveryMethodConfig slackConfig = new SlackNotificationDeliveryMethodConfig(); SlackNotificationDeliveryMethodConfig slackConfig = new SlackNotificationDeliveryMethodConfig();
@ -540,7 +484,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTemplateConfig config = new NotificationTemplateConfig(); NotificationTemplateConfig config = new NotificationTemplateConfig();
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate(); SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
slackNotificationTemplate.setEnabled(true); slackNotificationTemplate.setEnabled(true);
slackNotificationTemplate.setBody("To Slack :) ${recipientEmail}"); slackNotificationTemplate.setBody("To Slack :)");
config.setDeliveryMethodsTemplates(Map.of( config.setDeliveryMethodsTemplates(Map.of(
NotificationDeliveryMethod.SLACK, slackNotificationTemplate NotificationDeliveryMethod.SLACK, slackNotificationTemplate
)); ));
@ -553,14 +497,17 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationTarget.setTenantId(tenantId); notificationTarget.setTenantId(tenantId);
notificationTarget.setName(conversationName + " in Slack"); notificationTarget.setName(conversationName + " in Slack");
SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig(); SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig();
targetConfig.setConversation(new SlackConversation(conversationId, conversationName)); targetConfig.setConversation(SlackConversation.builder()
.id(conversationId)
.title(conversationName)
.build());
notificationTarget.setConfiguration(targetConfig); notificationTarget.setConfiguration(targetConfig);
notificationTarget = saveNotificationTarget(notificationTarget); notificationTarget = saveNotificationTarget(notificationTarget);
NotificationRequest successfulNotificationRequest = submitNotificationRequest(List.of(notificationTarget.getId()), notificationTemplate.getId(), 0); NotificationRequest successfulNotificationRequest = submitNotificationRequest(List.of(notificationTarget.getId()), notificationTemplate.getId(), 0);
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent()); .until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent());
verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq("To Slack :) ")); verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq(slackNotificationTemplate.getBody()));
NotificationRequestStats stats = getStats(successfulNotificationRequest.getId()); NotificationRequestStats stats = getStats(successfulNotificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.SLACK)).hasValue(1); assertThat(stats.getSent().get(NotificationDeliveryMethod.SLACK)).hasValue(1);

4
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -123,7 +123,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@Test @Test
public void testNotificationRuleProcessing_entityActionTrigger() throws Exception { public void testNotificationRuleProcessing_entityActionTrigger() throws Exception {
String notificationSubject = "${actionType}: ${entityType} [${entityId}]"; String notificationSubject = "${actionType}: ${entityType} [${entityId}]";
String notificationText = "User: ${originatorUserName}"; String notificationText = "User: ${userEmail}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.WEB); NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.WEB);
NotificationRule notificationRule = new NotificationRule(); NotificationRule notificationRule = new NotificationRule();
@ -132,7 +132,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig(); EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig();
triggerConfig.setEntityType(EntityType.DEVICE); triggerConfig.setEntityTypes(Set.of(EntityType.DEVICE));
triggerConfig.setCreated(true); triggerConfig.setCreated(true);
triggerConfig.setUpdated(true); triggerConfig.setUpdated(true);
triggerConfig.setDeleted(true); triggerConfig.setDeleted(true);

3
common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java

@ -33,10 +33,10 @@ import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse;
import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest; import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueClusterService; import org.thingsboard.server.queue.TbQueueClusterService;
@ -95,4 +95,5 @@ public interface TbClusterService extends TbQueueClusterService {
void pushEdgeSyncResponseToCore(FromEdgeSyncResponse fromEdgeSyncResponse); void pushEdgeSyncResponseToCore(FromEdgeSyncResponse fromEdgeSyncResponse);
void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action); void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action);
} }

5
common/cluster-api/src/main/proto/queue.proto

@ -968,6 +968,7 @@ message ToCoreMsg {
EdgeNotificationMsgProto edgeNotificationMsg = 5; EdgeNotificationMsgProto edgeNotificationMsg = 5;
DeviceActivityProto deviceActivityMsg = 6; DeviceActivityProto deviceActivityMsg = 6;
NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7; NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7;
NotificationRuleProcessorMsg notificationRuleProcessorMsg = 8;
} }
/* High priority messages with low latency are handled by ThingsBoard Core Service separately */ /* High priority messages with low latency are handled by ThingsBoard Core Service separately */
@ -1053,3 +1054,7 @@ message NotificationSchedulerServiceMsg {
int64 requestIdLSB = 4; int64 requestIdLSB = 4;
int64 ts = 5; int64 ts = 5;
} }
message NotificationRuleProcessorMsg {
bytes trigger = 1;
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java

@ -19,7 +19,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;

1
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;

28
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleProcessingService.java

@ -1,28 +0,0 @@
/**
* Copyright © 2016-2023 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.dao.notification;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.msg.TbMsg;
public interface NotificationRuleProcessingService {
void process(TenantId tenantId, NotificationRuleTrigger trigger);
void process(TenantId tenantId, TbMsg ruleEngineMsg);
}

6
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java

@ -20,8 +20,10 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -41,9 +43,9 @@ public interface NotificationTargetService {
PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink); PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink);
int countRecipientsForNotificationTargetConfig(TenantId tenantId, NotificationTargetConfig targetConfig); PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, PlatformUsersNotificationTargetConfig targetConfig, PageLink pageLink);
PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, CustomerId customerId, NotificationTargetConfig targetConfig, PageLink pageLink); PageData<User> findRecipientsForRuleNotificationTargetConfig(TenantId tenantId, PlatformUsersNotificationTargetConfig targetConfig, RuleOriginatedNotificationInfo info, PageLink pageLink);
void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id); void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id);

2
common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java

@ -24,7 +24,7 @@ public enum ApiFeature {
JS("jsExecutionApiState", "JavaScript functions execution"), JS("jsExecutionApiState", "JavaScript functions execution"),
EMAIL("emailApiState", "Email messages"), EMAIL("emailApiState", "Email messages"),
SMS("smsApiState", "SMS messages"), SMS("smsApiState", "SMS messages"),
ALARM("alarmApiState", "Created alarms"); ALARM("alarmApiState", "Alarms");
@Getter @Getter
private final String apiStateKey; private final String apiStateKey;

21
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java

@ -19,14 +19,14 @@ import lombok.Getter;
public enum ApiUsageRecordKey { public enum ApiUsageRecordKey {
TRANSPORT_MSG_COUNT(ApiFeature.TRANSPORT, "transportMsgCount", "transportMsgLimit"), TRANSPORT_MSG_COUNT(ApiFeature.TRANSPORT, "transportMsgCount", "transportMsgLimit", "message"),
TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"), TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit", "data point"),
STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"), STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit", "data point"),
RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit", "Rule Engine execution"),
JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"), JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit", "JavaScript execution"),
EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"), EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit", "email message"),
SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"), SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit", "SMS message"),
CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit"); CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit", "alarm");
private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT};
private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT};
@ -42,11 +42,14 @@ public enum ApiUsageRecordKey {
private final String apiCountKey; private final String apiCountKey;
@Getter @Getter
private final String apiLimitKey; private final String apiLimitKey;
@Getter
private final String unitLabel;
ApiUsageRecordKey(ApiFeature apiFeature, String apiCountKey, String apiLimitKey) { ApiUsageRecordKey(ApiFeature apiFeature, String apiCountKey, String apiLimitKey, String unitLabel) {
this.apiFeature = apiFeature; this.apiFeature = apiFeature;
this.apiCountKey = apiCountKey; this.apiCountKey = apiCountKey;
this.apiLimitKey = apiLimitKey; this.apiLimitKey = apiLimitKey;
this.unitLabel = unitLabel;
} }
public static ApiUsageRecordKey[] getKeys(ApiFeature feature) { public static ApiUsageRecordKey[] getKeys(ApiFeature feature) {

48
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordState.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2023 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;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApiUsageRecordState implements Serializable {
private final ApiFeature apiFeature;
private final ApiUsageRecordKey key;
private final long threshold;
private final long value;
public String getValueAsString() {
return valueAsString(value);
}
public String getThresholdAsString() {
return valueAsString(threshold);
}
private String valueAsString(long value) {
if (value > 1_000_000 && value % 1_000_000 < 10_000) {
return value / 1_000_000 + "M";
} else if (value > 10_000) {
return String.format("%.2fM", ((double) value) / 1_000_000);
} else {
return value + "";
}
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java

@ -19,9 +19,11 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import java.io.Serializable;
@ApiModel @ApiModel
@Data @Data
public class UpdateMessage { public class UpdateMessage implements Serializable {
@ApiModelProperty(position = 1, value = "'True' if new platform update is available.") @ApiModelProperty(position = 1, value = "'True' if new platform update is available.")
private final boolean updateAvailable; private final boolean updateAvailable;

20
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -30,6 +30,8 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@ApiModel @ApiModel
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId, NotificationRecipient { public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId, NotificationRecipient {
@ -165,6 +167,24 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
return getEmail(); return getEmail();
} }
@JsonIgnore
public String getTitle() {
String title = "";
if (isNotEmpty(firstName)) {
title += firstName;
}
if (isNotEmpty(lastName)) {
if (!title.isEmpty()) {
title += " ";
}
title += lastName;
}
if (title.isEmpty()) {
title = email;
}
return title;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();

8
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmApiCallResult.java → common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmApiCallResult.java

@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.alarm; package org.thingsboard.server.common.data.alarm;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import java.io.Serializable;
import java.util.List; import java.util.List;
@Data @Data
public class AlarmApiCallResult { public class AlarmApiCallResult implements Serializable {
private final boolean successful; private final boolean successful;
private final boolean created; private final boolean created;

3
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestPreview.java

@ -16,7 +16,6 @@
package org.thingsboard.server.common.data.notification; package org.thingsboard.server.common.data.notification;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import java.util.Collection; import java.util.Collection;
@ -28,6 +27,6 @@ public class NotificationRequestPreview {
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates; private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates;
private int totalRecipientsCount; private int totalRecipientsCount;
private Map<String, Integer> recipientsCountByTarget; private Map<String, Integer> recipientsCountByTarget;
private Collection<User> recipientsPreview; private Collection<String> recipientsPreview;
} }

14
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java

@ -19,7 +19,6 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import java.util.Collections; import java.util.Collections;
@ -33,6 +32,7 @@ public class NotificationRequestStats {
private final Map<NotificationDeliveryMethod, AtomicInteger> sent; private final Map<NotificationDeliveryMethod, AtomicInteger> sent;
private final Map<NotificationDeliveryMethod, Map<String, String>> errors; private final Map<NotificationDeliveryMethod, Map<String, String>> errors;
private String error;
@JsonIgnore @JsonIgnore
private final Map<NotificationDeliveryMethod, Set<Object>> processedRecipients; private final Map<NotificationDeliveryMethod, Set<Object>> processedRecipients;
@ -44,9 +44,11 @@ public class NotificationRequestStats {
@JsonCreator @JsonCreator
public NotificationRequestStats(@JsonProperty("sent") Map<NotificationDeliveryMethod, AtomicInteger> sent, public NotificationRequestStats(@JsonProperty("sent") Map<NotificationDeliveryMethod, AtomicInteger> sent,
@JsonProperty("errors") Map<NotificationDeliveryMethod, Map<String, String>> errors) { @JsonProperty("errors") Map<NotificationDeliveryMethod, Map<String, String>> errors,
@JsonProperty("error") String error) {
this.sent = sent; this.sent = sent;
this.errors = errors; this.errors = errors;
this.error = error;
this.processedRecipients = Collections.emptyMap(); this.processedRecipients = Collections.emptyMap();
} }
@ -60,13 +62,7 @@ public class NotificationRequestStats {
return; return;
} }
String errorMessage = error.getMessage(); String errorMessage = error.getMessage();
String key; errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getTitle(), errorMessage);
if (recipient instanceof User) {
key = ((User) recipient).getEmail();
} else {
key = "";
}
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(key, errorMessage);
} }
public boolean contains(NotificationDeliveryMethod deliveryMethod, Object recipientId) { public boolean contains(NotificationDeliveryMethod deliveryMethod, Object recipientId) {

6
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java

@ -19,12 +19,14 @@ public enum NotificationType {
GENERAL, GENERAL,
ALARM, ALARM,
DEVICE_INACTIVITY, DEVICE_ACTIVITY,
ENTITY_ACTION, ENTITY_ACTION,
ALARM_COMMENT, ALARM_COMMENT,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT, ALARM_ASSIGNMENT,
NEW_PLATFORM_VERSION, NEW_PLATFORM_VERSION,
ENTITIES_LIMIT ENTITIES_LIMIT,
API_USAGE_LIMIT,
RULE_ENGINE
} }

16
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java

@ -37,11 +37,15 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificationInfo { public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificationInfo {
private String action; private String action;
private String assigneeEmail;
private String assigneeFirstName; private String assigneeFirstName;
private String assigneeLastName; private String assigneeLastName;
private String assigneeEmail;
private UserId assigneeId; private UserId assigneeId;
private String userName;
private String userEmail;
private String userFirstName;
private String userLastName;
private String alarmType; private String alarmType;
private UUID alarmId; private UUID alarmId;
@ -59,7 +63,9 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
"assigneeLastName", assigneeLastName, "assigneeLastName", assigneeLastName,
"assigneeEmail", assigneeEmail, "assigneeEmail", assigneeEmail,
"assigneeId", assigneeId != null ? assigneeId.toString() : null, "assigneeId", assigneeId != null ? assigneeId.toString() : null,
"userName", userName, "userEmail", userEmail,
"userFirstName", userFirstName,
"userLastName", userLastName,
"alarmType", alarmType, "alarmType", alarmType,
"alarmId", alarmId.toString(), "alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.name().toLowerCase(), "alarmSeverity", alarmSeverity.name().toLowerCase(),
@ -71,12 +77,12 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
} }
@Override @Override
public CustomerId getOriginatorEntityCustomerId() { public CustomerId getAffectedCustomerId() {
return alarmCustomerId; return alarmCustomerId;
} }
@Override @Override
public UserId getTargetUserId() { public UserId getAffectedUserId() {
return assigneeId; return assigneeId;
} }

11
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java

@ -37,7 +37,10 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI
private String comment; private String comment;
private String action; private String action;
private String userName;
private String userEmail;
private String userFirstName;
private String userLastName;
private String alarmType; private String alarmType;
private UUID alarmId; private UUID alarmId;
@ -52,7 +55,9 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI
return mapOf( return mapOf(
"comment", comment, "comment", comment,
"action", action, "action", action,
"userName", userName, "userEmail", userEmail,
"userFirstName", userFirstName,
"userLastName", userLastName,
"alarmType", alarmType, "alarmType", alarmType,
"alarmId", alarmId.toString(), "alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.name().toLowerCase(), "alarmSeverity", alarmSeverity.name().toLowerCase(),
@ -64,7 +69,7 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI
} }
@Override @Override
public CustomerId getOriginatorEntityCustomerId() { public CustomerId getAffectedCustomerId() {
return alarmCustomerId; return alarmCustomerId;
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmNotificationInfo.java

@ -59,7 +59,7 @@ public class AlarmNotificationInfo implements RuleOriginatedNotificationInfo {
} }
@Override @Override
public CustomerId getOriginatorEntityCustomerId() { public CustomerId getAffectedCustomerId() {
return alarmCustomerId; return alarmCustomerId;
} }

63
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/ApiUsageLimitNotificationInfo.java

@ -0,0 +1,63 @@
/**
* Copyright © 2016-2023 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 org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Map;
import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ApiUsageLimitNotificationInfo implements RuleOriginatedNotificationInfo {
private ApiFeature feature;
private ApiUsageRecordKey recordKey;
private ApiUsageStateValue status;
private String limit;
private String currentValue;
private TenantId tenantId;
private String tenantName;
@Override
public Map<String, String> getTemplateData() {
return mapOf(
"feature", feature.getLabel(),
"unitLabel", recordKey.getUnitLabel(),
"status", status.name().toLowerCase(),
"limit", limit,
"currentValue", currentValue,
"tenantId", tenantId.toString(),
"tenantName", tenantName
);
}
@Override
public TenantId getAffectedTenantId() {
return tenantId;
}
}

14
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/DeviceInactivityNotificationInfo.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/info/DeviceActivityNotificationInfo.java

@ -32,22 +32,19 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificationInfo { public class DeviceActivityNotificationInfo implements RuleOriginatedNotificationInfo {
private String eventType;
private UUID deviceId; private UUID deviceId;
private String deviceName; private String deviceName;
private String deviceLabel; private String deviceLabel;
private String deviceType; private String deviceType;
private CustomerId deviceCustomerId; private CustomerId deviceCustomerId;
@Override
public CustomerId getOriginatorEntityCustomerId() {
return deviceCustomerId;
}
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return mapOf( return mapOf(
"eventType", eventType,
"deviceId", deviceId.toString(), "deviceId", deviceId.toString(),
"deviceName", deviceName, "deviceName", deviceName,
"deviceLabel", deviceLabel, "deviceLabel", deviceLabel,
@ -55,6 +52,11 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
); );
} }
@Override
public CustomerId getAffectedCustomerId() {
return deviceCustomerId;
}
@Override @Override
public EntityId getStateEntityId() { public EntityId getStateEntityId() {
return new DeviceId(deviceId); return new DeviceId(deviceId);

7
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntitiesLimitNotificationInfo.java

@ -30,7 +30,7 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class EntitiesLimitNotificationInfo implements NotificationInfo { public class EntitiesLimitNotificationInfo implements RuleOriginatedNotificationInfo {
private EntityType entityType; private EntityType entityType;
private long currentCount; private long currentCount;
@ -51,4 +51,9 @@ public class EntitiesLimitNotificationInfo implements NotificationInfo {
); );
} }
@Override
public TenantId getAffectedTenantId() {
return tenantId;
}
} }

21
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java

@ -37,14 +37,12 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
private EntityId entityId; private EntityId entityId;
private String entityName; private String entityName;
private ActionType actionType; private ActionType actionType;
private UUID originatorUserId;
private String originatorUserName;
private CustomerId entityCustomerId; private CustomerId entityCustomerId;
@Override private UUID userId;
public CustomerId getOriginatorEntityCustomerId() { private String userEmail;
return entityCustomerId; private String userFirstName;
} private String userLastName;
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
@ -53,11 +51,18 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
"entityId", entityId.toString(), "entityId", entityId.toString(),
"entityName", entityName, "entityName", entityName,
"actionType", actionType.name().toLowerCase(), "actionType", actionType.name().toLowerCase(),
"originatorUserId", originatorUserId.toString(), "userId", userId.toString(),
"originatorUserName", originatorUserName "userEmail", userEmail,
"userFirstName", userFirstName,
"userLastName", userLastName
); );
} }
@Override
public CustomerId getAffectedCustomerId() {
return entityCustomerId;
}
@Override @Override
public EntityId getStateEntityId() { public EntityId getStateEntityId() {
return entityId; return entityId;

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/NewPlatformVersionNotificationInfo.java

@ -29,7 +29,7 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
public class NewPlatformVersionNotificationInfo implements NotificationInfo { public class NewPlatformVersionNotificationInfo implements RuleOriginatedNotificationInfo {
private Map<String, String> message; private Map<String, String> message;

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineComponentLifecycleEventNotificationInfo.java

@ -31,7 +31,7 @@ import static org.thingsboard.server.common.data.util.CollectionsUtil.mapOf;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Builder @Builder
public class RuleEngineComponentLifecycleEventNotificationInfo implements NotificationInfo { public class RuleEngineComponentLifecycleEventNotificationInfo implements RuleOriginatedNotificationInfo {
private RuleChainId ruleChainId; private RuleChainId ruleChainId;
private String ruleChainName; private String ruleChainName;

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleEngineOriginatedNotificationInfo.java

@ -33,10 +33,13 @@ public class RuleEngineOriginatedNotificationInfo implements NotificationInfo {
private EntityId msgOriginator; private EntityId msgOriginator;
private String msgType; private String msgType;
private Map<String, String> msgMetadata; private Map<String, String> msgMetadata;
private Map<String, String> msgData;
@Override @Override
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
Map<String, String> templateData = new HashMap<>(msgMetadata); Map<String, String> templateData = new HashMap<>();
templateData.putAll(msgMetadata);
templateData.putAll(msgData);
templateData.put("originatorType", msgOriginator.getEntityType().getNormalName()); templateData.put("originatorType", msgOriginator.getEntityType().getNormalName());
templateData.put("originatorId", msgOriginator.getId().toString()); templateData.put("originatorId", msgOriginator.getId().toString());
templateData.put("msgType", msgType); templateData.put("msgType", msgType);

11
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/RuleOriginatedNotificationInfo.java

@ -16,13 +16,20 @@
package org.thingsboard.server.common.data.notification.info; package org.thingsboard.server.common.data.notification.info;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
public interface RuleOriginatedNotificationInfo extends NotificationInfo { public interface RuleOriginatedNotificationInfo extends NotificationInfo {
CustomerId getOriginatorEntityCustomerId(); default CustomerId getAffectedCustomerId() {
return null;
}
default UserId getAffectedUserId() {
return null;
}
default UserId getTargetUserId() { default TenantId getAffectedTenantId() {
return null; return null;
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/AlarmNotificationRuleTriggerConfig.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.Set; import java.util.Set;
@Data @Data
@ -38,7 +39,7 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
} }
@Data @Data
public static class ClearRule { public static class ClearRule implements Serializable {
private Set<AlarmSearchStatus> alarmStatuses; private Set<AlarmSearchStatus> alarmStatuses;
} }

12
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceInactivityNotificationRuleTriggerConfig.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/ApiUsageLimitNotificationRuleTriggerConfig.java

@ -16,20 +16,20 @@
package org.thingsboard.server.common.data.notification.rule.trigger; package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageStateValue;
import java.util.Set; import java.util.Set;
import java.util.UUID;
@Data @Data
public class DeviceInactivityNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { public class ApiUsageLimitNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private Set<UUID> devices; private Set<ApiFeature> apiFeatures;
private Set<UUID> deviceProfiles; private Set<ApiUsageStateValue> notifyOn;
// set either devices or profiles
@Override @Override
public NotificationRuleTriggerType getTriggerType() { public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.DEVICE_INACTIVITY; return NotificationRuleTriggerType.API_USAGE_LIMIT;
} }
} }

22
common/data/src/main/java/org/thingsboard/server/common/data/notification/info/UserOriginatedNotificationInfo.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/DeviceActivityNotificationRuleTriggerConfig.java

@ -13,21 +13,29 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.data.notification.info; package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Data; import lombok.Data;
import java.util.Collections; import javax.validation.constraints.NotEmpty;
import java.util.Map; import java.util.Set;
import java.util.UUID;
@Data @Data
public class UserOriginatedNotificationInfo implements NotificationInfo { public class DeviceActivityNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private String description; private Set<UUID> devices;
private Set<UUID> deviceProfiles; // set either devices or profiles
@NotEmpty
private Set<DeviceEvent> notifyOn;
@Override @Override
public Map<String, String> getTemplateData() { public NotificationRuleTriggerType getTriggerType() {
return Collections.emptyMap(); return NotificationRuleTriggerType.DEVICE_ACTIVITY;
}
public enum DeviceEvent {
ACTIVE, INACTIVE
} }
} }

4
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/EntityActionNotificationRuleTriggerConfig.java

@ -18,10 +18,12 @@ package org.thingsboard.server.common.data.notification.rule.trigger;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.Set;
@Data @Data
public class EntityActionNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig { public class EntityActionNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private EntityType entityType; // maybe add name filter ? private Set<EntityType> entityTypes; // maybe add name filter ?
private boolean created; private boolean created;
private boolean updated; private boolean updated;
private boolean deleted; private boolean deleted;

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerConfig.java

@ -26,13 +26,14 @@ import java.io.Serializable;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "triggerType") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "triggerType")
@JsonSubTypes({ @JsonSubTypes({
@Type(value = AlarmNotificationRuleTriggerConfig.class, name = "ALARM"), @Type(value = AlarmNotificationRuleTriggerConfig.class, name = "ALARM"),
@Type(value = DeviceInactivityNotificationRuleTriggerConfig.class, name = "DEVICE_INACTIVITY"), @Type(value = DeviceActivityNotificationRuleTriggerConfig.class, name = "DEVICE_ACTIVITY"),
@Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION"), @Type(value = EntityActionNotificationRuleTriggerConfig.class, name = "ENTITY_ACTION"),
@Type(value = AlarmCommentNotificationRuleTriggerConfig.class, name = "ALARM_COMMENT"), @Type(value = AlarmCommentNotificationRuleTriggerConfig.class, name = "ALARM_COMMENT"),
@Type(value = RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.class, name = "RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT"), @Type(value = RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.class, name = "RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT"),
@Type(value = AlarmAssignmentNotificationRuleTriggerConfig.class, name = "ALARM_ASSIGNMENT"), @Type(value = AlarmAssignmentNotificationRuleTriggerConfig.class, name = "ALARM_ASSIGNMENT"),
@Type(value = NewPlatformVersionNotificationRuleTriggerConfig.class, name = "NEW_PLATFORM_VERSION"), @Type(value = NewPlatformVersionNotificationRuleTriggerConfig.class, name = "NEW_PLATFORM_VERSION"),
@Type(value = EntitiesLimitNotificationRuleTriggerConfig.class, name = "ENTITIES_LIMIT") @Type(value = EntitiesLimitNotificationRuleTriggerConfig.class, name = "ENTITIES_LIMIT"),
@Type(value = ApiUsageLimitNotificationRuleTriggerConfig.class, name = "API_USAGE_LIMIT"),
}) })
public interface NotificationRuleTriggerConfig extends Serializable { public interface NotificationRuleTriggerConfig extends Serializable {

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTriggerType.java

@ -22,12 +22,13 @@ public enum NotificationRuleTriggerType {
ALARM, ALARM,
ALARM_COMMENT, ALARM_COMMENT,
DEVICE_INACTIVITY, DEVICE_ACTIVITY,
ENTITY_ACTION, ENTITY_ACTION,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT, ALARM_ASSIGNMENT,
NEW_PLATFORM_VERSION(false), NEW_PLATFORM_VERSION(false),
ENTITIES_LIMIT(false); ENTITIES_LIMIT(false),
API_USAGE_LIMIT(false);
private final boolean tenantLevel; private final boolean tenantLevel;

8
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationRecipient.java

@ -19,4 +19,12 @@ public interface NotificationRecipient {
Object getId(); Object getId();
String getTitle();
String getFirstName();
String getLastName();
String getEmail();
} }

28
common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java

@ -15,21 +15,47 @@
*/ */
package org.thingsboard.server.common.data.notification.targets.slack; package org.thingsboard.server.common.data.notification.targets.slack;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import static org.apache.commons.lang3.StringUtils.isEmpty;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder
public class SlackConversation implements NotificationRecipient { public class SlackConversation implements NotificationRecipient {
@NotEmpty @NotEmpty
private String id; private String id;
@NotEmpty @NotEmpty
private String name; private String title;
private String shortName;
private String wholeName;
private String email;
@JsonIgnore
@Override
public String getFirstName() {
String firstName = StringUtils.contains(wholeName, " ") ? wholeName.split(" ")[0] : wholeName;
if (isEmpty(firstName)) {
firstName = shortName;
}
return firstName;
}
@JsonIgnore
@Override
public String getLastName() {
return StringUtils.contains(wholeName, " ") ? wholeName.split(" ")[1] : null;
}
} }

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java

@ -25,7 +25,6 @@ import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@ -55,4 +54,8 @@ public abstract class DeliveryMethodNotificationTemplate {
@JsonIgnore @JsonIgnore
public abstract DeliveryMethodNotificationTemplate copy(); public abstract DeliveryMethodNotificationTemplate copy();
public boolean containsAny(String... params) {
return StringUtils.containsAny(body, params);
}
} }

6
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java

@ -19,6 +19,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
@ -47,4 +48,9 @@ public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotif
return new EmailDeliveryMethodNotificationTemplate(this); return new EmailDeliveryMethodNotificationTemplate(this);
} }
@Override
public boolean containsAny(String... params) {
return super.containsAny(params) || StringUtils.containsAny(subject, params);
}
} }

50
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/WebDeliveryMethodNotificationTemplate.java

@ -15,14 +15,19 @@
*/ */
package org.thingsboard.server.common.data.notification.template; package org.thingsboard.server.common.data.notification.template;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import java.util.Optional;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@ -37,7 +42,44 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific
public WebDeliveryMethodNotificationTemplate(WebDeliveryMethodNotificationTemplate other) { public WebDeliveryMethodNotificationTemplate(WebDeliveryMethodNotificationTemplate other) {
super(other); super(other);
this.subject = other.subject; this.subject = other.subject;
this.additionalConfig = other.additionalConfig; this.additionalConfig = other.additionalConfig != null ? other.additionalConfig.deepCopy() : null;
}
@JsonIgnore
public String getButtonText() {
return getButtonConfigProperty("text");
}
@JsonIgnore
public void setButtonText(String buttonText) {
getButtonConfig().ifPresent(buttonConfig -> {
buttonConfig.set("text", new TextNode(buttonText));
});
}
@JsonIgnore
public String getButtonLink() {
return getButtonConfigProperty("link");
}
@JsonIgnore
public void setButtonLink(String buttonLink) {
getButtonConfig().ifPresent(buttonConfig -> {
buttonConfig.set("link", new TextNode(buttonLink));
});
}
private String getButtonConfigProperty(String property) {
return getButtonConfig()
.map(buttonConfig -> buttonConfig.get(property))
.filter(JsonNode::isTextual)
.map(JsonNode::asText).orElse(null);
}
private Optional<ObjectNode> getButtonConfig() {
return Optional.ofNullable(additionalConfig)
.map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject)
.map(config -> (ObjectNode) config);
} }
@Override @Override
@ -50,4 +92,10 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific
return new WebDeliveryMethodNotificationTemplate(this); return new WebDeliveryMethodNotificationTemplate(this);
} }
@Override
public boolean containsAny(String... params) {
return super.containsAny(params) || StringUtils.containsAny(subject, params)
|| StringUtils.containsAny(getButtonText(), params) || StringUtils.containsAny(getButtonLink(), params);
}
} }

56
common/data/src/main/java/org/thingsboard/server/common/data/util/TemplateUtils.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2023 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.util;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import static com.google.common.base.Strings.nullToEmpty;
import static org.apache.commons.lang3.StringUtils.removeStart;
public class TemplateUtils {
private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\$\\{(.+?)(:[a-zA-Z]+)?}");
private static final Map<String, UnaryOperator<String>> FUNCTIONS = Map.of(
"upperCase", String::toUpperCase,
"lowerCase", String::toLowerCase,
"capitalize", StringUtils::capitalize
);
private TemplateUtils() {}
public static String processTemplate(String template, Map<String, String> context) {
return TEMPLATE_PARAM_PATTERN.matcher(template).replaceAll(matchResult -> {
String key = matchResult.group(1);
if (!context.containsKey(key)) {
return "\\" + matchResult.group();
}
String value = nullToEmpty(context.get(key));
String function = removeStart(matchResult.group(2), ":");
if (function != null) {
if (FUNCTIONS.containsKey(function)) {
value = FUNCTIONS.get(function).apply(value);
}
}
return value;
});
}
}

13
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateMailMessage.java → common/message/src/main/java/org/thingsboard/server/common/msg/notification/NotificationRuleProcessor.java

@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.msg.notification;
import lombok.Data; import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
public interface NotificationRuleProcessor {
void process(NotificationRuleTrigger trigger);
@Data
public class ApiUsageStateMailMessage {
private final ApiUsageRecordKey key;
private final long threshold;
private final long value;
} }

7
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/trigger/AlarmTrigger.java → common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/AlarmTrigger.java

@ -13,19 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.notification.trigger; package org.thingsboard.server.common.msg.notification.trigger;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
@Data @Data
@Builder @Builder
public class AlarmTrigger implements NotificationRuleTrigger { public class AlarmTrigger implements NotificationRuleTrigger {
private final TenantId tenantId;
private final AlarmApiCallResult alarmUpdate; private final AlarmApiCallResult alarmUpdate;
@Override @Override

49
common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/ApiUsageLimitTrigger.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2023 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.msg.notification.trigger;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.ApiUsageRecordState;
import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@Data
@Builder
public class ApiUsageLimitTrigger implements NotificationRuleTrigger {
private final TenantId tenantId;
private final ApiUsageRecordState state;
private final ApiUsageStateValue status;
@Override
public NotificationRuleTriggerType getType() {
return NotificationRuleTriggerType.API_USAGE_LIMIT;
}
@Override
public TenantId getTenantId() {
return tenantId;
}
@Override
public EntityId getOriginatorEntityId() {
return tenantId;
}
}

5
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/trigger/EntitiesLimitTrigger.java → common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntitiesLimitTrigger.java

@ -13,14 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.notification.trigger; package org.thingsboard.server.common.msg.notification.trigger;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@Data @Data
@ -39,7 +38,7 @@ public class EntitiesLimitTrigger implements NotificationRuleTrigger {
@Override @Override
public EntityId getOriginatorEntityId() { public EntityId getOriginatorEntityId() {
return TenantId.SYS_TENANT_ID; return tenantId;
} }
} }

8
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NewPlatformVersionTrigger.java → common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NewPlatformVersionTrigger.java

@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.data.notification.rule.trigger; package org.thingsboard.server.common.msg.notification.trigger;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@Data @Data
@Builder @Builder
@ -32,6 +33,11 @@ public class NewPlatformVersionTrigger implements NotificationRuleTrigger {
return NotificationRuleTriggerType.NEW_PLATFORM_VERSION; return NotificationRuleTriggerType.NEW_PLATFORM_VERSION;
} }
@Override
public TenantId getTenantId() {
return TenantId.SYS_TENANT_ID;
}
@Override @Override
public EntityId getOriginatorEntityId() { public EntityId getOriginatorEntityId() {
return TenantId.SYS_TENANT_ID; return TenantId.SYS_TENANT_ID;

10
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/trigger/NotificationRuleTrigger.java → common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/NotificationRuleTrigger.java

@ -13,14 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.data.notification.rule.trigger; package org.thingsboard.server.common.msg.notification.trigger;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
public interface NotificationRuleTrigger { import java.io.Serializable;
public interface NotificationRuleTrigger extends Serializable {
NotificationRuleTriggerType getType(); NotificationRuleTriggerType getType();
TenantId getTenantId();
EntityId getOriginatorEntityId(); EntityId getOriginatorEntityId();
} }

5
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/trigger/RuleEngineComponentLifecycleEventTrigger.java → common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/RuleEngineComponentLifecycleEventTrigger.java

@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.notification.trigger; package org.thingsboard.server.common.msg.notification.trigger;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@Builder @Builder
public class RuleEngineComponentLifecycleEventTrigger implements NotificationRuleTrigger { public class RuleEngineComponentLifecycleEventTrigger implements NotificationRuleTrigger {
private final TenantId tenantId;
private final RuleChainId ruleChainId; private final RuleChainId ruleChainId;
private final String ruleChainName; private final String ruleChainName;
private final EntityId componentId; private final EntityId componentId;

12
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/trigger/RuleEngineMsgTrigger.java → common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/RuleEngineMsgTrigger.java

@ -13,25 +13,29 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.notification.trigger; package org.thingsboard.server.common.msg.notification.trigger;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import java.util.Map;
@Data @Data
@Builder @Builder
public class RuleEngineMsgTrigger implements NotificationRuleTrigger { public class RuleEngineMsgTrigger implements NotificationRuleTrigger {
private final TenantId tenantId;
private final TbMsg msg; private final TbMsg msg;
private final NotificationRuleTriggerType triggerType;
public static Map<String, NotificationRuleTriggerType> msgTypeToTriggerType; // set on init by DefaultNotificationRuleProcessor
@Override @Override
public NotificationRuleTriggerType getType() { public NotificationRuleTriggerType getType() {
return triggerType; return msgTypeToTriggerType != null ? msgTypeToTriggerType.get(msg.getType()) : null;
} }
@Override @Override

5
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -192,6 +192,11 @@ public class HashPartitionService implements PartitionService {
return resolve(serviceType, null, tenantId, entityId); return resolve(serviceType, null, tenantId, entityId);
} }
@Override
public boolean isMyPartition(ServiceType serviceType, TenantId tenantId, EntityId entityId) {
return resolve(serviceType, tenantId, entityId).isMyPartition();
}
private TopicPartitionInfo resolve(QueueKey queueKey, EntityId entityId) { private TopicPartitionInfo resolve(QueueKey queueKey, EntityId entityId) {
int hash = hashFunction.newHasher() int hash = hashFunction.newHasher()
.putLong(entityId.getId().getMostSignificantBits()) .putLong(entityId.getId().getMostSignificantBits())

2
common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java

@ -35,6 +35,8 @@ public interface PartitionService {
TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId); TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId);
boolean isMyPartition(ServiceType serviceType, TenantId tenantId, EntityId entityId);
/** /**
* Received from the Discovery service when network topology is changed. * Received from the Discovery service when network topology is changed.
* @param currentService - current service information {@link org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo} * @param currentService - current service information {@link org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo}

55
common/queue/src/main/java/org/thingsboard/server/queue/notification/RemoteNotificationRuleProcessor.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2023 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.queue.notification;
import com.google.protobuf.ByteString;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.notification.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import java.util.UUID;
@Service
@ConditionalOnMissingBean(NotificationRuleProcessor.class)
@RequiredArgsConstructor
public class RemoteNotificationRuleProcessor implements NotificationRuleProcessor {
private final TbQueueProducerProvider producerProvider;
private final PartitionService partitionService;
private final DataDecodingEncodingService encodingService;
@Override
public void process(NotificationRuleTrigger trigger) {
TransportProtos.NotificationRuleProcessorMsg.Builder msg = TransportProtos.NotificationRuleProcessorMsg.newBuilder()
.setTrigger(ByteString.copyFrom(encodingService.encode(trigger)));
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, trigger.getTenantId(), trigger.getOriginatorEntityId());
producerProvider.getTbCoreMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(),
TransportProtos.ToCoreMsg.newBuilder()
.setNotificationRuleProcessorMsg(msg)
.build()), null);
}
}

22
common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java

@ -32,7 +32,10 @@ import org.thingsboard.server.common.data.kv.KvEntry;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
@ -237,6 +240,25 @@ public class JacksonUtil {
} }
} }
public static Map<String, String> toFlatMap(JsonNode node) {
HashMap<String, String> map = new HashMap<>();
toFlatMap(node, "", map);
return map;
}
private static void toFlatMap(JsonNode node, String currentPath, Map<String, String> map) {
if (node.isObject()) {
Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
currentPath = currentPath.isEmpty() ? "" : currentPath + ".";
while (fields.hasNext()) {
Map.Entry<String, JsonNode> entry = fields.next();
toFlatMap(entry.getValue(), currentPath + entry.getKey(), map);
}
} else if (node.isValueNode()) {
map.put(currentPath, node.asText());
}
}
public static void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) { public static void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) {
addKvEntry(entityNode, kvEntry, kvEntry.getKey()); addKvEntry(entityNode, kvEntry, kvEntry.getKey());
} }

1
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.alarm;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;

1
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -27,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmModificationRequest; import org.thingsboard.server.common.data.alarm.AlarmModificationRequest;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest;

12
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.notification;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.HasId;
@ -32,9 +33,11 @@ import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheKey; import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheKey;
import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheValue; import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheValue;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -96,6 +99,7 @@ public class DefaultNotificationRuleService extends AbstractCachedEntityService<
.getNotificationRules(); .getNotificationRules();
} }
@Transactional
@Override @Override
public void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id) { public void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id) {
NotificationRule notificationRule = findNotificationRuleById(tenantId, id); NotificationRule notificationRule = findNotificationRuleById(tenantId, id);
@ -106,6 +110,14 @@ public class DefaultNotificationRuleService extends AbstractCachedEntityService<
@Override @Override
public void deleteNotificationRulesByTenantId(TenantId tenantId) { public void deleteNotificationRulesByTenantId(TenantId tenantId) {
notificationRuleDao.removeByTenantId(tenantId); notificationRuleDao.removeByTenantId(tenantId);
List<NotificationRuleCacheKey> cacheKeys = Arrays.stream(NotificationRuleTriggerType.values())
.map(triggerType -> NotificationRuleCacheKey.builder()
.tenantId(tenantId)
.triggerType(triggerType)
.build())
.collect(Collectors.toList());
cache.evict(cacheKeys);
} }
@Override @Override

85
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java

@ -24,6 +24,7 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
@ -40,7 +41,9 @@ import org.thingsboard.server.common.data.notification.rule.trigger.AlarmAssignm
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmCommentNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceInactivityNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.ApiUsageLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityNotificationRuleTriggerConfig.DeviceEvent;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
@ -124,61 +127,83 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
NotificationTarget sysAdmins = createTarget(tenantId, "System administrators", new SystemAdministratorsFilter(), "All system administrators"); NotificationTarget sysAdmins = createTarget(tenantId, "System administrators", new SystemAdministratorsFilter(), "All system administrators");
NotificationTarget affectedTenantAdmins = createTarget(tenantId, "Affected tenant's administrators", new AffectedTenantAdministratorsFilter(), ""); NotificationTarget affectedTenantAdmins = createTarget(tenantId, "Affected tenant's administrators", new AffectedTenantAdministratorsFilter(), "");
NotificationTemplate entitiesLimitNotificationTemplate = createTemplate(tenantId, "Entities limit notification", NotificationType.ENTITIES_LIMIT, NotificationTemplate entitiesLimitNotificationTemplate = createTemplate(tenantId, "Entities count limit notification", NotificationType.ENTITIES_LIMIT,
"${entityType}s limit will be reached soon for tenant ${tenantName}", "${entityType}s limit will be reached soon for tenant ${tenantName}",
"${entityType}s usage: ${currentCount}/${limit} (${percents}%)"); "${entityType}s usage: ${currentCount}/${limit} (${percents}%)");
EntitiesLimitNotificationRuleTriggerConfig entitiesLimitRuleTriggerConfig = new EntitiesLimitNotificationRuleTriggerConfig(); EntitiesLimitNotificationRuleTriggerConfig entitiesLimitRuleTriggerConfig = new EntitiesLimitNotificationRuleTriggerConfig();
entitiesLimitRuleTriggerConfig.setEntityTypes(null); entitiesLimitRuleTriggerConfig.setEntityTypes(null);
entitiesLimitRuleTriggerConfig.setThreshold(0.8f); entitiesLimitRuleTriggerConfig.setThreshold(0.8f);
createRule(tenantId, "Entities limit", entitiesLimitNotificationTemplate.getId(), entitiesLimitRuleTriggerConfig, createRule(tenantId, "Entities count limit", entitiesLimitNotificationTemplate.getId(), entitiesLimitRuleTriggerConfig,
List.of(affectedTenantAdmins.getId(), sysAdmins.getId()), "Send notification to tenant admins when count of entities of some type reached 80% threshold of the limit"); List.of(affectedTenantAdmins.getId(), sysAdmins.getId()), "Send notification to tenant admins and system admins when count of entities of some type reached 80% threshold of the limit");
NotificationTemplate apiUsageLimitNotificationTemplate = createTemplate(tenantId, "API usage limit notification", NotificationType.API_USAGE_LIMIT,
"${feature} feature - ${status:upperCase}",
"Tenant '${tenantName}': usage - ${currentValue} out of ${limit} ${unitLabel}s");
ApiUsageLimitNotificationRuleTriggerConfig apiUsageLimitRuleTriggerConfig = new ApiUsageLimitNotificationRuleTriggerConfig();
apiUsageLimitRuleTriggerConfig.setApiFeatures(null);
apiUsageLimitRuleTriggerConfig.setNotifyOn(Set.of(ApiUsageStateValue.WARNING, ApiUsageStateValue.DISABLED));
createRule(tenantId, "API usage limit", apiUsageLimitNotificationTemplate.getId(), apiUsageLimitRuleTriggerConfig,
List.of(affectedTenantAdmins.getId(), sysAdmins.getId()), "Send notification to tenant admins and system admins when API feature usage state changed");
return; return;
} }
NotificationTarget originatorEntityOwnerUsers = createTarget(tenantId, "Users of rule trigger entity's owner", new OriginatorEntityOwnerUsersFilter(), NotificationTarget originatorEntityOwnerUsers = createTarget(tenantId, "Users of the entity owner", new OriginatorEntityOwnerUsersFilter(),
"Customer users in case trigger entity (e.g. alarm) has customer, tenant admins otherwise"); "In case trigger entity (e.g. created device or alarm) is owned by customer, then recipients are this customer's users, otherwise tenant admins");
NotificationTarget affectedUser = createTarget(tenantId, "Affected user", new AffectedUserFilter(), NotificationTarget affectedUser = createTarget(tenantId, "Affected user", new AffectedUserFilter(),
"If rule trigger is an action that affects some user (e.g. alarm assigned to user) - this user"); "If rule trigger is an action that affects some user (e.g. alarm assigned to user) - this user");
NotificationTemplate alarmNotificationTemplate = createTemplate(tenantId, "Alarm notification", NotificationType.ALARM, NotificationTemplate newAlarmNotificationTemplate = createTemplate(tenantId, "New alarm notification", NotificationType.ALARM,
"New alarm '${alarmType}'",
"Severity: ${alarmSeverity}, originator: ${alarmOriginatorEntityType} '${alarmOriginatorName}'",
"notifications", null, null);
AlarmNotificationRuleTriggerConfig newAlarmRuleTriggerConfig = new AlarmNotificationRuleTriggerConfig();
newAlarmRuleTriggerConfig.setAlarmTypes(null);
newAlarmRuleTriggerConfig.setAlarmSeverities(null);
newAlarmRuleTriggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED));
createRule(tenantId, "New alarm", newAlarmNotificationTemplate.getId(), newAlarmRuleTriggerConfig,
List.of(tenantAdmins.getId(), originatorEntityOwnerUsers.getId()), "Send notification to tenant admins and alarm's customer users " +
"when an alarm is created");
NotificationTemplate alarmUpdateNotificationTemplate = createTemplate(tenantId, "Alarm update notification", NotificationType.ALARM,
"Alarm '${alarmType}' - ${action}", "Alarm '${alarmType}' - ${action}",
"Severity: ${alarmSeverity}, originator: ${alarmOriginatorEntityType} '${alarmOriginatorName}'", "Severity: ${alarmSeverity}, originator: ${alarmOriginatorEntityType} '${alarmOriginatorName}'",
"notifications", null, null); "notifications", null, null);
AlarmNotificationRuleTriggerConfig alarmRuleTriggerConfig = new AlarmNotificationRuleTriggerConfig(); AlarmNotificationRuleTriggerConfig alarmRuleTriggerConfig = new AlarmNotificationRuleTriggerConfig();
alarmRuleTriggerConfig.setAlarmTypes(null); alarmRuleTriggerConfig.setAlarmTypes(null);
alarmRuleTriggerConfig.setAlarmSeverities(null); alarmRuleTriggerConfig.setAlarmSeverities(null);
alarmRuleTriggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED)); alarmRuleTriggerConfig.setNotifyOn(Set.of(AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED));
createRule(tenantId, "Alarm", alarmNotificationTemplate.getId(), alarmRuleTriggerConfig, createRule(tenantId, "Alarm update", alarmUpdateNotificationTemplate.getId(), alarmRuleTriggerConfig,
List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " + List.of(tenantAdmins.getId(), originatorEntityOwnerUsers.getId()), "Send notification to tenant admins and alarm's customer users " +
"when any alarm is created, updated or cleared"); "when any alarm is updated or cleared");
NotificationTemplate deviceActionNotificationTemplate = createTemplate(tenantId, "Device action notification", NotificationType.ENTITY_ACTION, NotificationTemplate deviceActionNotificationTemplate = createTemplate(tenantId, "Device action notification", NotificationType.ENTITY_ACTION,
"${entityType} was ${actionType}", "${entityType} was ${actionType}",
"${entityType} '${entityName}' was ${actionType} by user ${originatorUserName}", "${entityType} '${entityName}' was ${actionType} by user ${userEmail}",
"info", "Go to Device", "/devices/${entityId}"); "info", "Go to device", "/devices/${entityId}");
EntityActionNotificationRuleTriggerConfig deviceActionRuleTriggerConfig = new EntityActionNotificationRuleTriggerConfig(); EntityActionNotificationRuleTriggerConfig deviceActionRuleTriggerConfig = new EntityActionNotificationRuleTriggerConfig();
deviceActionRuleTriggerConfig.setEntityType(EntityType.DEVICE); deviceActionRuleTriggerConfig.setEntityTypes(Set.of(EntityType.DEVICE));
deviceActionRuleTriggerConfig.setCreated(true); deviceActionRuleTriggerConfig.setCreated(true);
deviceActionRuleTriggerConfig.setUpdated(false); deviceActionRuleTriggerConfig.setUpdated(false);
deviceActionRuleTriggerConfig.setDeleted(true); deviceActionRuleTriggerConfig.setDeleted(false);
createRule(tenantId, "Device created or deleted", deviceActionNotificationTemplate.getId(), deviceActionRuleTriggerConfig, createRule(tenantId, "Device created", deviceActionNotificationTemplate.getId(), deviceActionRuleTriggerConfig,
List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " + List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " +
"when device is created or deleted"); "when device is created");
NotificationTemplate deviceInactivityNotificationTemplate = createTemplate(tenantId, "Device inactivity notification", NotificationType.DEVICE_INACTIVITY, NotificationTemplate deviceActivityNotificationTemplate = createTemplate(tenantId, "Device activity notification", NotificationType.DEVICE_ACTIVITY,
"Device '${deviceName}' inactive", "Device '${deviceName}' became ${eventType}",
"Device '${deviceName}' with type '${deviceType}' became inactive", "Device '${deviceName}' of type '${deviceType}' is now ${eventType}",
"info", "Go to Device", "/devices/${deviceId}"); "info", "Go to device", "/devices/${deviceId}");
DeviceInactivityNotificationRuleTriggerConfig deviceInactivityRuleTriggerConfig = new DeviceInactivityNotificationRuleTriggerConfig(); DeviceActivityNotificationRuleTriggerConfig deviceActivityRuleTriggerConfig = new DeviceActivityNotificationRuleTriggerConfig();
deviceInactivityRuleTriggerConfig.setDevices(null); deviceActivityRuleTriggerConfig.setDevices(null);
deviceInactivityRuleTriggerConfig.setDeviceProfiles(null); deviceActivityRuleTriggerConfig.setDeviceProfiles(null);
createRule(tenantId, "Device became inactive", deviceInactivityNotificationTemplate.getId(), deviceInactivityRuleTriggerConfig, deviceActivityRuleTriggerConfig.setNotifyOn(Set.of(DeviceEvent.ACTIVE, DeviceEvent.INACTIVE));
createRule(tenantId, "Device activity status change", deviceActivityNotificationTemplate.getId(), deviceActivityRuleTriggerConfig,
List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " + List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " +
"when any device became inactive"); "when any device changes its activity state");
NotificationTemplate alarmCommentNotificationTemplate = createTemplate(tenantId, "Alarm comment notification", NotificationType.ALARM_COMMENT, NotificationTemplate alarmCommentNotificationTemplate = createTemplate(tenantId, "Alarm comment notification", NotificationType.ALARM_COMMENT,
"Comment on '${alarmType}' alarm", "Comment on '${alarmType}' alarm",
"${userName} ${action} comment: ${comment}", "${userEmail} ${action} comment: ${comment}",
"people", null, null); "people", null, null);
AlarmCommentNotificationRuleTriggerConfig alarmCommentRuleTriggerConfig = new AlarmCommentNotificationRuleTriggerConfig(); AlarmCommentNotificationRuleTriggerConfig alarmCommentRuleTriggerConfig = new AlarmCommentNotificationRuleTriggerConfig();
alarmCommentRuleTriggerConfig.setAlarmTypes(null); alarmCommentRuleTriggerConfig.setAlarmTypes(null);
@ -192,7 +217,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
NotificationTemplate alarmAssignedNotificationTemplate = createTemplate(tenantId, "Alarm assigned notification", NotificationType.ALARM_ASSIGNMENT, NotificationTemplate alarmAssignedNotificationTemplate = createTemplate(tenantId, "Alarm assigned notification", NotificationType.ALARM_ASSIGNMENT,
"Alarm '${alarmType}' (${alarmSeverity}) was assigned to user", "Alarm '${alarmType}' (${alarmSeverity}) was assigned to user",
"${userName} assigned alarm on ${alarmOriginatorEntityType} '${alarmOriginatorName}' to ${assigneeEmail}", "${userEmail} assigned alarm on ${alarmOriginatorEntityType} '${alarmOriginatorName}' to ${assigneeEmail}",
"person", null, null); "person", null, null);
AlarmAssignmentNotificationRuleTriggerConfig alarmAssignmentRuleTriggerConfig = new AlarmAssignmentNotificationRuleTriggerConfig(); AlarmAssignmentNotificationRuleTriggerConfig alarmAssignmentRuleTriggerConfig = new AlarmAssignmentNotificationRuleTriggerConfig();
alarmAssignmentRuleTriggerConfig.setAlarmTypes(null); alarmAssignmentRuleTriggerConfig.setAlarmTypes(null);
@ -203,9 +228,9 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
List.of(affectedUser.getId()), "Send notification to user when any alarm was assigned to him"); List.of(affectedUser.getId()), "Send notification to user when any alarm was assigned to him");
NotificationTemplate ruleEngineComponentLifecycleFailureNotificationTemplate = createTemplate(tenantId, "Rule chain/node lifecycle failure notification", NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT, NotificationTemplate ruleEngineComponentLifecycleFailureNotificationTemplate = createTemplate(tenantId, "Rule chain/node lifecycle failure notification", NotificationType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
"${action:capitalize} failure in Rule chain '${ruleChainName}'",
"${componentType} '${componentName}' failed to ${action}", "${componentType} '${componentName}' failed to ${action}",
"Rule chain '${ruleChainName}' - ${action} failure:<br/>${error}", "warning", "Go to rule chain", "/ruleChains/${ruleChainId}");
"warning", "Go to Rule chain", "/ruleChains/${ruleChainId}");
RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig ruleEngineComponentLifecycleEventRuleTriggerConfig = new RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig(); RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig ruleEngineComponentLifecycleEventRuleTriggerConfig = new RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig();
ruleEngineComponentLifecycleEventRuleTriggerConfig.setRuleChains(null); ruleEngineComponentLifecycleEventRuleTriggerConfig.setRuleChains(null);
ruleEngineComponentLifecycleEventRuleTriggerConfig.setRuleChainEvents(Set.of(ComponentLifecycleEvent.STARTED, ComponentLifecycleEvent.UPDATED, ComponentLifecycleEvent.STOPPED)); ruleEngineComponentLifecycleEventRuleTriggerConfig.setRuleChainEvents(Set.of(ComponentLifecycleEvent.STARTED, ComponentLifecycleEvent.UPDATED, ComponentLifecycleEvent.STOPPED));

42
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
@ -97,20 +98,12 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
NotificationTarget notificationTarget = findNotificationTargetById(tenantId, targetId); NotificationTarget notificationTarget = findNotificationTargetById(tenantId, targetId);
Objects.requireNonNull(notificationTarget, "Notification target [" + targetId + "] not found"); Objects.requireNonNull(notificationTarget, "Notification target [" + targetId + "] not found");
NotificationTargetConfig configuration = notificationTarget.getConfiguration(); NotificationTargetConfig configuration = notificationTarget.getConfiguration();
return findRecipientsForNotificationTargetConfig(tenantId, customerId, configuration, pageLink); return findRecipientsForNotificationTargetConfig(tenantId, (PlatformUsersNotificationTargetConfig) configuration, pageLink);
} }
@Override @Override
public int countRecipientsForNotificationTargetConfig(TenantId tenantId, NotificationTargetConfig targetConfig) { public PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, PlatformUsersNotificationTargetConfig targetConfig, PageLink pageLink) {
return (int) findRecipientsForNotificationTargetConfig(tenantId, null, targetConfig, new PageLink(1)).getTotalElements(); UsersFilter usersFilter = targetConfig.getUsersFilter();
}
@Override
public PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, CustomerId customerId, NotificationTargetConfig targetConfig, PageLink pageLink) {
if (!(targetConfig instanceof PlatformUsersNotificationTargetConfig)) {
throw new IllegalArgumentException("Unsupported target type " + targetConfig.getType());
}
UsersFilter usersFilter = ((PlatformUsersNotificationTargetConfig) targetConfig).getUsersFilter();
switch (usersFilter.getType()) { switch (usersFilter.getType()) {
case USER_LIST: { case USER_LIST: {
List<User> users = ((UserListFilter) usersFilter).getUsersIds().stream() List<User> users = ((UserListFilter) usersFilter).getUsersIds().stream()
@ -142,8 +135,6 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
} }
} }
} }
case AFFECTED_TENANT_ADMINISTRATORS:
return userService.findTenantAdmins(tenantId, pageLink);
case SYSTEM_ADMINISTRATORS: case SYSTEM_ADMINISTRATORS:
return userService.findSysAdmins(pageLink); return userService.findSysAdmins(pageLink);
case ALL_USERS: { case ALL_USERS: {
@ -153,13 +144,34 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
return userService.findAllUsers(pageLink); return userService.findAllUsers(pageLink);
} }
} }
case ORIGINATOR_ENTITY_OWNER_USERS: { }
return new PageData<>();
}
@Override
public PageData<User> findRecipientsForRuleNotificationTargetConfig(TenantId tenantId, PlatformUsersNotificationTargetConfig targetConfig, RuleOriginatedNotificationInfo info, PageLink pageLink) {
switch (targetConfig.getUsersFilter().getType()) {
case ORIGINATOR_ENTITY_OWNER_USERS:
CustomerId customerId = info.getAffectedCustomerId();
if (customerId != null && !customerId.isNullUid()) { if (customerId != null && !customerId.isNullUid()) {
return userService.findCustomerUsers(tenantId, customerId, pageLink); return userService.findCustomerUsers(tenantId, customerId, pageLink);
} else { } else {
return userService.findTenantAdmins(tenantId, pageLink); return userService.findTenantAdmins(tenantId, pageLink);
} }
} case AFFECTED_USER:
UserId userId = info.getAffectedUserId();
if (userId != null) {
return new PageData<>(List.of(userService.findUserById(tenantId, userId)), 1, 1, false);
}
case AFFECTED_TENANT_ADMINISTRATORS:
TenantId affectedTenantId = info.getAffectedTenantId();
if (affectedTenantId == null) {
affectedTenantId = tenantId;
}
if (!affectedTenantId.isNullUid()) {
return userService.findTenantAdmins(affectedTenantId, pageLink);
}
break;
} }
return new PageData<>(); return new PageData<>();
} }

4
dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java

@ -32,8 +32,8 @@ public class AdminSettingsDataValidator extends DataValidator<AdminSettings> {
@Override @Override
protected void validateCreate(TenantId tenantId, AdminSettings adminSettings) { protected void validateCreate(TenantId tenantId, AdminSettings adminSettings) {
AdminSettings existentAdminSettingsWithKey = adminSettingsService.findAdminSettingsByKey(tenantId, adminSettings.getKey()); AdminSettings existingSettings = adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, adminSettings.getKey());
if (existentAdminSettingsWithKey != null) { if (existingSettings != null) {
throw new DataValidationException("Admin settings with such name already exists!"); throw new DataValidationException("Admin settings with such name already exists!");
} }
} }

2
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -48,7 +48,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.sql.AlarmEntity; import org.thingsboard.server.dao.model.sql.AlarmEntity;

3
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java

@ -63,7 +63,8 @@ public interface NotificationRepository extends JpaRepository<NotificationEntity
@Modifying @Modifying
@Transactional @Transactional
@Query("UPDATE NotificationEntity n SET n.status = :status WHERE n.recipientId = :recipientId") @Query("UPDATE NotificationEntity n SET n.status = :status " +
"WHERE n.recipientId = :recipientId AND n.status <> :status")
int updateStatusByRecipientId(@Param("recipientId") UUID recipientId, int updateStatusByRecipientId(@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status); @Param("status") NotificationStatus status);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save