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.CassandraBufferedRateWriteExecutor;
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.NotificationTargetService;
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.queue.discovery.PartitionService;
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.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
@ -341,7 +341,7 @@ public class ActorSystemContext {
@Autowired
@Getter
private NotificationRuleProcessingService notificationRuleProcessingService;
private NotificationRuleProcessor notificationRuleProcessor;
@Autowired
@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.plugin.ComponentLifecycleEvent;
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> {
@ -50,7 +50,8 @@ public abstract class RuleEngineComponentActor<T extends EntityId, P extends Com
}
private void processNotificationRule(ComponentLifecycleEvent event, Throwable e) {
systemContext.getNotificationRuleProcessingService().process(tenantId, RuleEngineComponentLifecycleEventTrigger.builder()
systemContext.getNotificationRuleProcessor().process(RuleEngineComponentLifecycleEventTrigger.builder()
.tenantId(tenantId)
.ruleChainId(getRuleChainId())
.ruleChainName(getRuleChainName())
.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.RuleChainAwareMsg;
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.queue.PartitionChangeMsg;
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);
tbMsg.getCallback().onSuccess();
}
systemContext.getNotificationRuleProcessingService().process(tenantId, tbMsg);
systemContext.getNotificationRuleProcessor().process(RuleEngineMsgTrigger.builder()
.tenantId(tenantId)
.msg(tbMsg)
.build());
}
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.NotificationRequestInfo;
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.targets.NotificationRecipient;
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.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.NotificationTemplate;
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 javax.validation.Valid;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
@ -187,14 +188,12 @@ public class NotificationController extends BaseController {
checkEntity(notificationRequest.getId(), notificationRequest, NOTIFICATION);
notificationRequest.setOriginatorEntityId(user.getId());
if (notificationRequest.getInfo() != null && !(notificationRequest.getInfo() instanceof UserOriginatedNotificationInfo)) {
throw new IllegalArgumentException("Unsupported notification info type");
}
notificationRequest.setInfo(null);
notificationRequest.setRuleId(null);
notificationRequest.setStatus(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")
@ -217,45 +216,49 @@ public class NotificationController extends BaseController {
NotificationProcessingContext tmpProcessingCtx = NotificationProcessingContext.builder()
.tenantId(user.getTenantId())
.request(request)
.settings(null)
.template(template)
.settings(null)
.build();
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = tmpProcessingCtx.getDeliveryMethods().stream()
.collect(Collectors.toMap(m -> m, deliveryMethod -> {
Map<String, String> templateContext;
NotificationRecipient recipient = null;
if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) {
templateContext = tmpProcessingCtx.createTemplateContext(user);
} else {
templateContext = Collections.emptyMap();
recipient = userService.findUserById(user.getTenantId(), user.getId());
}
return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, templateContext);
return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, recipient);
}));
preview.setProcessedTemplates(processedTemplates);
// generic permission
Set<User> recipientsPreview = new LinkedHashSet<>();
Set<String> recipientsPreview = new LinkedHashSet<>();
Map<String, Integer> recipientsCountByTarget = new HashMap<>();
List<NotificationTarget> targets = notificationTargetService.findNotificationTargetsByTenantIdAndIds(user.getTenantId(),
request.getTargets().stream().map(NotificationTargetId::new).collect(Collectors.toList()));
for (NotificationTarget target : targets) {
int recipientsCount;
List<NotificationRecipient> recipientsPart;
if (target.getConfiguration().getType() == NotificationTargetType.PLATFORM_USERS) {
PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null,
target.getConfiguration(), new PageLink(recipientsPreviewSize));
PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(),
(PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize));
recipientsCount = (int) recipients.getTotalElements();
for (User recipient : recipients.getData()) {
if (recipientsPreview.size() < recipientsPreviewSize) {
recipientsPreview.add(recipient);
} else {
break;
}
}
recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList());
} else {
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);
}
preview.setRecipientsPreview(recipientsPreview);
preview.setRecipientsCountByTarget(recipientsCountByTarget);
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);
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), null, notificationTarget.getConfiguration(), pageLink);
return notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), (PlatformUsersNotificationTargetConfig) notificationTarget.getConfiguration(), pageLink);
}
@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 lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
@ -136,16 +137,20 @@ public class NotificationTemplateController extends BaseController {
@GetMapping("/slack/conversations")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversationType type,
@RequestParam(required = false) String token,
@AuthenticationPrincipal SecurityUser user) {
// generic permission
NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId());
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
if (slackConfig == null) {
throw new IllegalArgumentException("Slack is not configured");
if (StringUtils.isEmpty(token)) {
NotificationSettings settings = notificationSettingsService.findNotificationSettings(user.getTenantId());
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
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.createOAuth2Templates();
systemDataLoaderService.createQueues();
systemDataLoaderService.createDefaultNotificationConfigs();
// systemDataLoaderService.loadSystemPlugins();
// systemDataLoaderService.loadSystemRules();
installScripts.loadSystemLwm2mResources();

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

@ -142,6 +142,13 @@ public class EntityActionService {
if (user != null) {
metaData.putValue("userId", user.getId().toString());
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()) {
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.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiFeature;
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.ApiUsageStateMailMessage;
import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.EntityType;
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.TopicPartitionInfo;
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.TenantService;
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.discovery.PartitionService;
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.telemetry.InternalTelemetryService;
@ -79,8 +80,6 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -108,8 +107,9 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
private final ApiUsageStateService apiUsageStateService;
private final TbTenantProfileCache tenantProfileCache;
private final MailService mailService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
private final NotificationRuleProcessor notificationRuleProcessor;
private final DbCallbackExecutorService dbExecutor;
private final MailExecutorService mailExecutor;
@Lazy
@Autowired
@ -130,8 +130,6 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
private final Lock updateLock = new ReentrantLock();
private final ExecutorService mailExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("api-usage-svc-mail"));
@PostConstruct
public void init() {
super.init();
@ -340,32 +338,35 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK);
if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) {
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(() -> {
try {
mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage((TenantApiUsageState) state, apiFeature, stateValue));
mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, recordState);
} catch (ThingsboardException 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);
for (ApiUsageRecordKey apiUsageRecordKey : ApiUsageRecordKey.getKeys(apiFeature)) {
long threshold = state.getProfileThreshold(apiUsageRecordKey);
long warnThreshold = state.getProfileWarnThreshold(apiUsageRecordKey);
long value = state.get(apiUsageRecordKey);
if (checker.check(threshold, warnThreshold, value)) {
return new ApiUsageStateMailMessage(apiUsageRecordKey, threshold, value);
return new ApiUsageRecordState(apiFeature, apiUsageRecordKey, threshold, value);
}
}
return null;
@ -377,7 +378,7 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
} else if (ApiUsageStateValue.WARNING.equals(stateValue)) {
return (t, wt, v) -> v < t && v >= wt;
} 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
private void destroy() {
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.TenantId;
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 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.ApiFeature;
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.StringUtils;
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 TARGET_EMAIL = "targetEmail";
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 Configuration freemarkerConfig;
@ -335,7 +333,7 @@ public class DefaultMailService implements MailService {
}
@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);
Map<String, Object> model = new HashMap<>();
@ -350,11 +348,11 @@ public class DefaultMailService implements MailService {
message = mergeTemplateIntoString("state.enabled.ftl", model);
break;
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);
break;
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);
break;
}
@ -406,10 +404,10 @@ public class DefaultMailService implements MailService {
}
}
private String toWarningValueLabel(ApiUsageRecordKey key, long value, long threshold) {
String valueInM = getValueAsString(value);
String thresholdInM = getValueAsString(threshold);
switch (key) {
private String toWarningValueLabel(ApiUsageRecordState recordState) {
String valueInM = recordState.getValueAsString();
String thresholdInM = recordState.getThresholdAsString();
switch (recordState.getKey()) {
case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT:
return valueInM + " out of " + thresholdInM + " allowed data points";
@ -428,36 +426,26 @@ public class DefaultMailService implements MailService {
}
}
private String toDisabledValueLabel(ApiUsageRecordKey key, long value) {
switch (key) {
private String toDisabledValueLabel(ApiUsageRecordState recordState) {
switch (recordState.getKey()) {
case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT:
return getValueAsString(value) + " data points";
return recordState.getValueAsString() + " data points";
case TRANSPORT_MSG_COUNT:
return getValueAsString(value) + " messages";
return recordState.getValueAsString() + " messages";
case JS_EXEC_COUNT:
return "JavaScript functions " + getValueAsString(value) + " times";
return "JavaScript functions " + recordState.getValueAsString() + " times";
case RE_EXEC_COUNT:
return getValueAsString(value) + " Rule Engine messages";
return recordState.getValueAsString() + " Rule Engine messages";
case EMAIL_EXEC_COUNT:
return getValueAsString(value) + " Email messages";
return recordState.getValueAsString() + " Email messages";
case SMS_EXEC_COUNT:
return getValueAsString(value) + " SMS messages";
return recordState.getValueAsString() + " SMS messages";
default:
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,
String subject, String message, long timeout) throws ThingsboardException {
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.stereotype.Service;
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.SmsService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationId;
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.TenantId;
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.NotificationTarget;
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.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -84,6 +82,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Service
@ -103,61 +102,59 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider;
private final RateLimitService rateLimitService;
private final MailService mailService;
private final SmsService smsService;
private Map<NotificationDeliveryMethod, NotificationChannel> channels;
@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)) {
throw new TbRateLimitsException(EntityType.TENANT);
}
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
NotificationTemplate notificationTemplate;
if (notificationRequest.getTemplateId() != null) {
notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, notificationRequest.getTemplateId());
if (request.getTemplateId() != null) {
notificationTemplate = notificationTemplateService.findNotificationTemplateById(tenantId, request.getTemplateId());
} else {
notificationTemplate = notificationRequest.getTemplate();
notificationTemplate = request.getTemplate();
}
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());
Set<NotificationDeliveryMethod> availableDeliveryMethods = getAvailableDeliveryMethods(tenantId);
NotificationRuleId ruleId = request.getRuleId();
notificationTemplate.getConfiguration().getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (!template.isEnabled()) return;
if (!availableDeliveryMethods.contains(deliveryMethod)) {
throw new IllegalArgumentException("Settings for " + deliveryMethod.getName() + " are missing");
if (!channels.get(deliveryMethod).check(tenantId)) {
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))) {
throw new IllegalArgumentException("Target for " + deliveryMethod.getName() + " delivery method is missing");
}
}
});
if (notificationRequest.getAdditionalConfig() != null) {
NotificationRequestConfig config = notificationRequest.getAdditionalConfig();
if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) {
notificationRequest.setStatus(NotificationRequestStatus.SCHEDULED);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
forwardToNotificationSchedulerService(tenantId, savedNotificationRequest.getId());
return savedNotificationRequest;
if (request.getAdditionalConfig() != null) {
NotificationRequestConfig config = request.getAdditionalConfig();
if (config.getSendingDelayInSec() > 0 && request.getId() == null) {
request.setStatus(NotificationRequestStatus.SCHEDULED);
request = notificationRequestService.saveNotificationRequest(tenantId, request);
forwardToNotificationSchedulerService(tenantId, request.getId());
return request;
}
}
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
log.debug("Processing notification request (tenantId: {}, targets: {})", tenantId, notificationRequest.getTargets());
notificationRequest.setStatus(NotificationRequestStatus.PROCESSING);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
log.debug("Processing notification request (tenantId: {}, targets: {})", tenantId, request.getTargets());
request.setStatus(NotificationRequestStatus.PROCESSING);
request = notificationRequestService.saveNotificationRequest(tenantId, request);
NotificationProcessingContext ctx = NotificationProcessingContext.builder()
.tenantId(tenantId)
.request(savedNotificationRequest)
.settings(settings)
.request(request)
.template(notificationTemplate)
.settings(settings)
.build();
notificationExecutor.submit(() -> {
@ -169,7 +166,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
}
Futures.whenAllComplete(results).run(() -> {
NotificationRequestId requestId = savedNotificationRequest.getId();
NotificationRequestId requestId = ctx.getRequest().getId();
log.debug("[{}] Notification request processing is finished", requestId);
NotificationRequestStats stats = ctx.getStats();
try {
@ -177,36 +174,39 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} catch (Exception 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);
});
return savedNotificationRequest;
return request;
}
private List<ListenableFuture<Void>> processForTarget(NotificationTarget target, NotificationProcessingContext ctx) {
Iterable<? extends NotificationRecipient> recipients;
switch (target.getConfiguration().getType()) {
case PLATFORM_USERS: {
PlatformUsersNotificationTargetConfig platformUsersTargetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration();
if (platformUsersTargetConfig.getUsersFilter().getType() == UsersFilterType.AFFECTED_USER) {
if (ctx.getRequest().getInfo() instanceof RuleOriginatedNotificationInfo) {
UserId targetUserId = ((RuleOriginatedNotificationInfo) ctx.getRequest().getInfo()).getTargetUserId();
if (targetUserId != null) {
recipients = List.of(userService.findUserById(ctx.getTenantId(), targetUserId));
break;
}
}
recipients = Collections.emptyList();
PlatformUsersNotificationTargetConfig targetConfig = (PlatformUsersNotificationTargetConfig) target.getConfiguration();
if (targetConfig.getUsersFilter().getType().isForRules()) {
recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink);
}, 500);
} else {
recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), ctx.getCustomerId(), platformUsersTargetConfig, pageLink);
return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink);
}, 500);
}
break;
}
case SLACK: {
SlackNotificationTargetConfig slackTargetConfig = (SlackNotificationTargetConfig) target.getConfiguration();
recipients = List.of(slackTargetConfig.getConversation());
SlackNotificationTargetConfig targetConfig = (SlackNotificationTargetConfig) target.getConfiguration();
recipients = List.of(targetConfig.getConversation());
break;
}
default: {
@ -239,15 +239,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
if (ctx.getStats().contains(deliveryMethod, recipient.getId())) {
return Futures.immediateFailedFuture(new AlreadySentException());
}
Map<String, String> templateContext;
if (recipient instanceof User) {
templateContext = ctx.createTemplateContext(((User) recipient));
} else {
templateContext = Collections.emptyMap();
}
DeliveryMethodNotificationTemplate processedTemplate;
try {
processedTemplate = ctx.getProcessedTemplate(deliveryMethod, templateContext);
processedTemplate = ctx.getProcessedTemplate(deliveryMethod, recipient);
} catch (Exception 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);
NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
.notificationId(notificationId)
.notificationId(notificationId.getId())
.newStatus(NotificationStatus.READ)
.build();
onNotificationUpdate(tenantId, recipientId, update);
@ -345,20 +340,15 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override
public Set<NotificationDeliveryMethod> getAvailableDeliveryMethods(TenantId tenantId) {
Set<NotificationDeliveryMethod> deliveryMethods = new HashSet<>();
deliveryMethods.add(NotificationDeliveryMethod.WEB);
NotificationSettings notificationSettings = notificationSettingsService.findNotificationSettings(tenantId);
if (notificationSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.SLACK)) {
deliveryMethods.add(NotificationDeliveryMethod.SLACK);
}
try {
mailService.testConnection(tenantId);
deliveryMethods.add(NotificationDeliveryMethod.EMAIL);
} catch (Exception e) {}
if (smsService.isConfigured(tenantId)) {
deliveryMethods.add(NotificationDeliveryMethod.SMS);
}
return deliveryMethods;
return channels.values().stream()
.filter(channel -> channel.check(tenantId))
.map(NotificationChannel::getDeliveryMethod)
.collect(Collectors.toSet());
}
@Override
public boolean check(TenantId tenantId) {
return true;
}
@Override
@ -374,6 +364,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
.deleted(true)
.build());
} else if (notificationRequest.isScheduled()) {
// TODO: just forward to scheduler service
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.NotificationRequestId;
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.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.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
@ -109,14 +110,12 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS
notificationExecutor.executeAsync(() -> {
try {
notificationCenter.processNotificationRequest(tenantId, notificationRequest);
notificationCenter.processNotificationRequest(tenantId, notificationRequest, null);
} catch (Exception e) {
log.error("Failed to process scheduled notification request {}", notificationRequest.getId(), e);
UserId senderId = notificationRequest.getSenderId();
if (senderId != null) {
notificationCenter.sendBasicNotification(tenantId, senderId, "Notification failure",
"Failed to process scheduled notification (request " + notificationRequest.getId() + "): " + e.getMessage());
}
NotificationRequestStats stats = new NotificationRequestStats();
stats.setError(e.getMessage());
notificationRequestService.updateNotificationRequest(tenantId, request.getId(), NotificationRequestStatus.SENT, stats);
}
});
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;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.Strings;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.apache.commons.collections4.MapUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
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.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.HasSubject;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.util.TemplateUtils;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@SuppressWarnings("unchecked")
public class NotificationProcessingContext {
@ -62,11 +57,9 @@ public class NotificationProcessingContext {
@Getter
private final NotificationRequestStats stats;
private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\$\\{([a-zA-Z]+)(:[a-zA-Z]+)?}");
@Builder
public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationSettings settings,
NotificationTemplate template) {
public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationTemplate template, NotificationSettings settings) {
this.tenantId = tenantId;
this.request = request;
this.settings = settings;
@ -80,6 +73,7 @@ public class NotificationProcessingContext {
NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration();
templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
if (template.isEnabled()) {
template = processTemplate(template, null); // processing template with immutable params
templates.put(deliveryMethod, template);
}
});
@ -90,74 +84,54 @@ public class NotificationProcessingContext {
return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod);
}
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) {
NotificationInfo info = request.getInfo();
if (info != null) {
templateContext = new HashMap<>(templateContext);
templateContext.putAll(info.getTemplateData());
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) {
T template = (T) templates.get(deliveryMethod);
Map<String, String> additionalTemplateContext = null;
if (recipient != null) {
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();
template.setBody(processTemplate(template.getBody(), templateContext));
private <T extends DeliveryMethodNotificationTemplate> T processTemplate(T template, Map<String, String> additionalTemplateContext) {
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) {
String subject = ((HasSubject) template).getSubject();
((HasSubject) template).setSubject(processTemplate(subject, templateContext));
((HasSubject) template).setSubject(TemplateUtils.processTemplate(subject, templateContext));
}
if (deliveryMethod == NotificationDeliveryMethod.WEB) {
if (template instanceof WebDeliveryMethodNotificationTemplate) {
WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template;
Optional<ObjectNode> buttonConfig = Optional.ofNullable(webNotificationTemplate.getAdditionalConfig())
.map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject)
.map(config -> (ObjectNode) config);
if (buttonConfig.isPresent()) {
JsonNode text = buttonConfig.get().get("text");
if (text != null && text.isTextual()) {
text = new TextNode(processTemplate(text.asText(), 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);
}
String buttonText = webNotificationTemplate.getButtonText();
if (isNotEmpty(buttonText)) {
webNotificationTemplate.setButtonText(TemplateUtils.processTemplate(buttonText, templateContext));
}
String buttonLink = webNotificationTemplate.getButtonLink();
if (isNotEmpty(buttonLink)) {
webNotificationTemplate.setButtonLink(TemplateUtils.processTemplate(buttonLink, templateContext));
}
}
return template;
}
private static String processTemplate(String template, Map<String, String> context) {
return TEMPLATE_PARAM_PATTERN.matcher(template).replaceAll(matchResult -> {
String key = matchResult.group(1);
String value = Strings.nullToEmpty(context.get(key));
String function = matchResult.group(2);
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;
}
private Map<String, String> createTemplateContextForRecipient(NotificationRecipient recipient) {
return Map.of(
"recipientEmail", Strings.nullToEmpty(recipient.getEmail()),
"recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()),
"recipientLastName", Strings.nullToEmpty(recipient.getLastName())
);
}
}

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 org.springframework.stereotype.Component;
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.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.mail.MailExecutorService;
@ -35,11 +37,26 @@ public class EmailNotificationChannel implements NotificationChannel<User, Email
@Override
public ListenableFuture<Void> sendNotification(User recipient, EmailDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
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;
});
}
@Override
public boolean check(TenantId tenantId) {
try {
mailService.testConnection(tenantId);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
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;
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.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
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> {
ListenableFuture<Void> sendNotification(R recipient, T processedTemplate, NotificationProcessingContext ctx);
boolean check(TenantId tenantId);
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 org.springframework.stereotype.Component;
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.settings.NotificationSettings;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
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> {
private final SlackService slackService;
private final NotificationSettingsService notificationSettingsService;
private final ExternalCallExecutorService executor;
@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
public NotificationDeliveryMethod getDeliveryMethod() {
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.thingsboard.rule.engine.api.SmsService;
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.template.SmsDeliveryMethodNotificationTemplate;
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
public NotificationDeliveryMethod getDeliveryMethod() {
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.info.NotificationInfo;
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.NotificationRuleTriggerType;
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.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
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.notification.rule.trigger.NotificationRuleTriggerProcessor;
import org.thingsboard.server.service.notification.rule.trigger.RuleEngineMsgNotificationRuleTriggerProcessor;
@ -59,7 +58,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
@Slf4j
@SuppressWarnings({"rawtypes", "unchecked"})
public class DefaultNotificationRuleProcessingService implements NotificationRuleProcessingService {
public class DefaultNotificationRuleProcessor implements NotificationRuleProcessor {
private final NotificationRuleService notificationRuleService;
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<String, NotificationRuleTriggerType> ruleEngineMsgTypeToTriggerType = new HashMap<>();
@Override
public void process(TenantId tenantId, NotificationRuleTrigger trigger) {
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(
trigger.getType().isTenantLevel() ? tenantId : TenantId.SYS_TENANT_ID, trigger.getType());
public void process(NotificationRuleTrigger trigger) {
NotificationRuleTriggerType triggerType = 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) {
notificationExecutor.submit(() -> {
try {
processNotificationRule(tenantId, rule, trigger);
processNotificationRule(rule, trigger);
} catch (Throwable 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
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) {
private void processNotificationRule(NotificationRule rule, NotificationRuleTrigger trigger) {
NotificationRuleTriggerConfig triggerConfig = rule.getTriggerConfig();
log.debug("Processing notification rule '{}' for trigger type {}", rule.getName(), rule.getTriggerType());
if (matchesClearRule(trigger, triggerConfig)) {
List<NotificationRequest> notificationRequests = notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(tenantId, rule.getId(), trigger.getOriginatorEntityId());
List<NotificationRequest> notificationRequests = findAlreadySentNotificationRequests(rule, trigger);
if (notificationRequests.isEmpty()) {
return;
}
@ -113,11 +101,11 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
.flatMap(notificationRequest -> notificationRequest.getTargets().stream())
.distinct().collect(Collectors.toList());
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
submitNotificationRequest(tenantId, targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0);
submitNotificationRequest(targets, rule, trigger.getOriginatorEntityId(), notificationInfo, 0);
notificationRequests.forEach(notificationRequest -> {
if (notificationRequest.isScheduled()) {
notificationCenter.deleteNotificationRequest(tenantId, notificationRequest.getId());
notificationCenter.deleteNotificationRequest(rule.getTenantId(), notificationRequest.getId());
}
});
return;
@ -126,31 +114,23 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
if (matchesFilter(trigger, triggerConfig)) {
NotificationInfo notificationInfo = constructNotificationInfo(trigger, triggerConfig);
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) {
return triggerProcessors.get(triggerConfig.getTriggerType()).matchesFilter(trigger, triggerConfig);
private List<NotificationRequest> findAlreadySentNotificationRequests(NotificationRule rule, NotificationRuleTrigger trigger) {
return notificationRequestService.findNotificationRequestsByRuleIdAndOriginatorEntityId(rule.getTenantId(), rule.getId(), trigger.getOriginatorEntityId());
}
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, triggerConfig);
}
private void submitNotificationRequest(TenantId tenantId, List<UUID> targets, NotificationRule rule,
private void submitNotificationRequest(List<UUID> targets, NotificationRule rule,
EntityId originatorEntityId, NotificationInfo notificationInfo, int delayInSec) {
NotificationRequestConfig config = new NotificationRequestConfig();
if (delayInSec > 0) {
config.setSendingDelayInSec(delayInSec);
}
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId)
.tenantId(rule.getTenantId())
.targets(targets)
.templateId(rule.getTemplateId())
.additionalConfig(config)
@ -161,13 +141,25 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
notificationExecutor.submit(() -> {
try {
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) {
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)
public void onNotificationRuleDeleted(ComponentLifecycleMsg componentLifecycleMsg) {
if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED ||
@ -187,6 +179,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
@Autowired
public void setTriggerProcessors(Collection<NotificationRuleTriggerProcessor> processors) {
Map<String, NotificationRuleTriggerType> ruleEngineMsgTypeToTriggerType = new HashMap<>();
processors.forEach(processor -> {
triggerProcessors.put(processor.getTriggerType(), processor);
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.AlarmStatusFilter;
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.Action;
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;
@ -49,7 +49,7 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
}
@Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) {
public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
AlarmInfo alarmInfo = JacksonUtil.fromString(trigger.getMsg().getData(), AlarmInfo.class);
AlarmAssignee assignee = alarmInfo.getAssignee();
return AlarmAssignmentNotificationInfo.builder()
@ -58,7 +58,9 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
.assigneeLastName(assignee != null ? assignee.getLastName() : null)
.assigneeEmail(assignee != null ? assignee.getEmail() : 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())
.alarmType(alarmInfo.getType())
.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.AlarmStatusFilter;
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.NotificationRuleTriggerType;
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;
@ -59,14 +59,16 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
}
@Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, AlarmCommentNotificationRuleTriggerConfig triggerConfig) {
public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg();
AlarmComment comment = JacksonUtil.fromString(msg.getMetaData().getValue("comment"), AlarmComment.class);
AlarmInfo alarmInfo = JacksonUtil.fromString(msg.getData(), AlarmInfo.class);
return AlarmCommentNotificationInfo.builder()
.comment(comment.getComment().get("text").asText())
.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())
.alarmType(alarmInfo.getType())
.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.AlarmStatusFilter;
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.AlarmAction;
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.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.notification.trigger.AlarmTrigger;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@ -93,7 +93,7 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
}
@Override
public NotificationInfo constructNotificationInfo(AlarmTrigger trigger, AlarmNotificationRuleTriggerConfig triggerConfig) {
public RuleOriginatedNotificationInfo constructNotificationInfo(AlarmTrigger trigger) {
AlarmApiCallResult alarmUpdate = trigger.getAlarmUpdate();
AlarmInfo alarmInfo = alarmUpdate.getAlarm();
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.id.DeviceId;
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.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceInactivityNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.info.DeviceActivityNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
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.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 java.util.Set;
@Service
@RequiredArgsConstructor
public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<DeviceInactivityNotificationRuleTriggerConfig> {
public class DeviceActivityTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<DeviceActivityNotificationRuleTriggerConfig> {
private final TbDeviceProfileCache deviceProfileCache;
@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();
if (CollectionUtils.isNotEmpty(triggerConfig.getDevices())) {
return triggerConfig.getDevices().contains(deviceId.getId());
@ -52,9 +57,10 @@ public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificati
}
@Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, DeviceInactivityNotificationRuleTriggerConfig triggerConfig) {
public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg();
return DeviceInactivityNotificationInfo.builder()
return DeviceActivityNotificationInfo.builder()
.eventType(trigger.getMsg().getType().equals(DataConstants.ACTIVITY_EVENT) ? "active" : "inactive")
.deviceId(msg.getOriginator().getId())
.deviceName(msg.getMetaData().getValue("deviceName"))
.deviceType(msg.getMetaData().getValue("deviceType"))
@ -65,12 +71,12 @@ public class DeviceInactivityTriggerProcessor implements RuleEngineMsgNotificati
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.DEVICE_INACTIVITY;
return NotificationRuleTriggerType.DEVICE_ACTIVITY;
}
@Override
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 org.springframework.stereotype.Service;
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.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 static org.apache.commons.collections.CollectionUtils.isNotEmpty;
@ -41,7 +41,7 @@ public class EntitiesLimitTriggerProcessor implements NotificationRuleTriggerPro
}
@Override
public NotificationInfo constructNotificationInfo(EntitiesLimitTrigger trigger, EntitiesLimitNotificationRuleTriggerConfig triggerConfig) {
public RuleOriginatedNotificationInfo constructNotificationInfo(EntitiesLimitTrigger trigger) {
return EntitiesLimitNotificationInfo.builder()
.entityType(trigger.getEntityType())
.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.audit.ActionType;
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.NotificationRuleTriggerType;
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.Set;
import java.util.UUID;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service
public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<EntityActionNotificationRuleTriggerConfig> {
@ -51,11 +53,11 @@ public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRu
} else {
return false;
}
return triggerConfig.getEntityType() == null || getEntityType(trigger.getMsg()) == triggerConfig.getEntityType();
return isEmpty(triggerConfig.getEntityTypes()) || triggerConfig.getEntityTypes().contains(getEntityType(trigger.getMsg()));
}
@Override
public NotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) {
public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg();
String msgType = msg.getType();
ActionType actionType = msgType.equals(DataConstants.ENTITY_CREATED) ? ActionType.ADDED :
@ -65,8 +67,10 @@ public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRu
.entityId(msg.getOriginator())
.entityName(msg.getMetaData().getValue("entityName"))
.actionType(actionType)
.originatorUserId(UUID.fromString(msg.getMetaData().getValue("userId")))
.originatorUserName(msg.getMetaData().getValue("userName"))
.userId(UUID.fromString(msg.getMetaData().getValue("userId")))
.userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
.entityCustomerId(msg.getCustomerId())
.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.server.common.data.id.TenantId;
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.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.msg.queue.ServiceType;
import org.thingsboard.server.queue.discovery.PartitionService;
@ -44,7 +44,7 @@ public class NewPlatformVersionTriggerProcessor implements NotificationRuleTrigg
}
@Override
public NotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger, NewPlatformVersionNotificationRuleTriggerConfig triggerConfig) {
public RuleOriginatedNotificationInfo constructNotificationInfo(NewPlatformVersionTrigger trigger) {
return NewPlatformVersionNotificationInfo.builder()
.message(JacksonUtil.convertValue(trigger.getMessage(), new TypeReference<>() {}))
.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;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTrigger;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
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.NotificationRuleTriggerType;
@ -28,7 +28,7 @@ public interface NotificationRuleTriggerProcessor<T extends NotificationRuleTrig
return false;
}
NotificationInfo constructNotificationInfo(T trigger, C triggerConfig);
RuleOriginatedNotificationInfo constructNotificationInfo(T trigger);
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;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.stereotype.Service;
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.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.rule.trigger.RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.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.StringWriter;
import java.util.Set;
@Service
@RequiredArgsConstructor
public class RuleEngineComponentLifecycleEventTriggerProcessor implements NotificationRuleTriggerProcessor<RuleEngineComponentLifecycleEventTrigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig> {
private final PartitionService partitionService;
@Override
public boolean matchesFilter(RuleEngineComponentLifecycleEventTrigger trigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) {
if (CollectionUtils.isNotEmpty(triggerConfig.getRuleChains())) {
@ -41,6 +47,9 @@ public class RuleEngineComponentLifecycleEventTriggerProcessor implements Notifi
return false;
}
}
if (!partitionService.resolve(ServiceType.TB_RULE_ENGINE, trigger.getTenantId(), trigger.getComponentId()).isMyPartition()) {
return false;
}
EntityType componentType = trigger.getComponentId().getEntityType();
Set<ComponentLifecycleEvent> trackedEvents;
@ -68,7 +77,7 @@ public class RuleEngineComponentLifecycleEventTriggerProcessor implements Notifi
}
@Override
public NotificationInfo constructNotificationInfo(RuleEngineComponentLifecycleEventTrigger trigger, RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig triggerConfig) {
public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineComponentLifecycleEventTrigger trigger) {
return RuleEngineComponentLifecycleEventNotificationInfo.builder()
.ruleChainId(trigger.getRuleChainId())
.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;
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;

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.msg.MsgType;
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.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
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.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
@ -129,6 +131,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
private final OtaPackageStateService firmwareStateService;
private final GitVersionControlQueueService vcQueueService;
private final NotificationSchedulerService notificationSchedulerService;
private final NotificationRuleProcessor notificationRuleProcessor;
private final TbCoreConsumerStats stats;
protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer;
private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer;
@ -156,7 +159,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
PartitionService partitionService,
ApplicationEventPublisher eventPublisher,
Optional<JwtSettingsService> jwtSettingsService,
NotificationSchedulerService notificationSchedulerService) {
NotificationSchedulerService notificationSchedulerService,
NotificationRuleProcessor notificationRuleProcessor) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
@ -171,6 +175,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
this.firmwareStateService = firmwareStateService;
this.vcQueueService = vcQueueService;
this.notificationSchedulerService = notificationSchedulerService;
this.notificationRuleProcessor = notificationRuleProcessor;
}
@PostConstruct
@ -269,6 +274,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
TransportProtos.NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = toCoreMsg.getNotificationSchedulerServiceMsg();
log.trace("[{}] Forwarding message to notification scheduler service {}", id, toCoreMsg.getNotificationSchedulerServiceMsg());
forwardToNotificationSchedulerService(notificationSchedulerServiceMsg, callback);
} else if (toCoreMsg.hasNotificationRuleProcessorMsg()) {
Optional<NotificationRuleTrigger> notificationRuleTrigger = encodingService.decode(toCoreMsg.getNotificationRuleProcessorMsg().getTrigger().toByteArray());
notificationRuleTrigger.ifPresent(notificationRuleProcessor::process);
}
} catch (Throwable 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.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@Service
@RequiredArgsConstructor
public class DefaultSlackService implements SlackService {
@ -80,7 +82,14 @@ public class DefaultSlackService implements SlackService {
.map(user -> {
SlackConversation conversation = new SlackConversation();
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;
})
.collect(Collectors.toList());
@ -99,7 +108,9 @@ public class DefaultSlackService implements SlackService {
.map(channel -> {
SlackConversation conversation = new SlackConversation();
conversation.setId(channel.getId());
conversation.setName("#" + channel.getName());
conversation.setShortName(channel.getName());
conversation.setWholeName(channel.getNameNormalized());
conversation.setTitle("#" + channel.getName());
return conversation;
})
.collect(Collectors.toList());
@ -111,7 +122,7 @@ public class DefaultSlackService implements SlackService {
public SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern) {
List<SlackConversation> conversations = listConversations(tenantId, token, conversationType);
return conversations.stream()
.filter(conversation -> StringUtils.containsIgnoreCase(conversation.getName(), namePattern))
.filter(conversation -> StringUtils.containsIgnoreCase(conversation.getTitle(), namePattern))
.findFirst().orElse(null);
}
@ -144,7 +155,7 @@ public class DefaultSlackService implements SlackService {
String neededScope = response.getNeeded();
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;

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);
if (subscriptions != null) {
NotificationsSubscriptionUpdate subscriptionUpdate = new NotificationsSubscriptionUpdate(notificationUpdate);
log.trace("Handling notificationUpdate for user {}: {}", recipientId, notificationUpdate);
subscriptions.stream()
.filter(subscription -> subscription.getType() == TbSubscriptionType.NOTIFICATIONS
|| subscription.getType() == TbSubscriptionType.NOTIFICATIONS_COUNT)
.forEach(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);
}
});
.forEach(subscription -> onNotificationsSubUpdate(subscriptionUpdate, subscription));
}
callback.onSuccess();
}
@ -341,21 +332,37 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
if (entityId.getEntityType() != EntityType.USER) {
return;
}
log.trace("Handling notificationRequestUpdate for user {}: {}", entityId, notificationRequestUpdate);
subscriptions.forEach(subscription -> {
if (subscription.getType() != TbSubscriptionType.NOTIFICATIONS &&
subscription.getType() != TbSubscriptionType.NOTIFICATIONS_COUNT) {
return;
}
if (!subscription.getTenantId().equals(tenantId) || !subscription.getServiceId().equals(serviceId)) {
if (!subscription.getTenantId().equals(tenantId)) {
return;
}
localSubscriptionService.onSubscriptionUpdate(subscription.getSessionId(), subscription.getSubscriptionId(),
subscriptionUpdate, TbCallback.EMPTY);
onNotificationsSubUpdate(subscriptionUpdate, subscription);
});
});
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
public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, TbCallback callback) {
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 final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
@Autowired
@Lazy
@Autowired @Lazy
private WebSocketService wsService;
@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.server.common.data.ApiUsageRecordKey;
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.AlarmCommentType;
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.query.AlarmData;
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.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmOperationResult;
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.entitiy.alarm.TbAlarmCommentService;
import org.thingsboard.server.dao.notification.trigger.AlarmTrigger;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.Collection;
@ -70,7 +70,7 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
private final TbAlarmCommentService alarmCommentService;
private final TbApiUsageReportClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService;
private final NotificationRuleProcessingService notificationRuleProcessingService;
private final NotificationRuleProcessor notificationRuleProcessor;
@Override
protected String getExecutorPrefix() {
@ -235,7 +235,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm);
});
}
notificationRuleProcessingService.process(tenantId, AlarmTrigger.builder()
notificationRuleProcessor.process(AlarmTrigger.builder()
.tenantId(tenantId)
.alarmUpdate(result)
.build());
});
@ -252,7 +253,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm);
});
}
notificationRuleProcessingService.process(tenantId, AlarmTrigger.builder()
notificationRuleProcessor.process(AlarmTrigger.builder()
.tenantId(tenantId)
.alarmUpdate(result)
.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.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.dao.notification.NotificationRuleProcessingService;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct;
@ -62,7 +61,7 @@ public class DefaultUpdateService implements UpdateService {
private BuildProperties buildProperties;
@Autowired
private NotificationRuleProcessingService notificationRuleProcessingService;
private NotificationRuleProcessor notificationRuleProcessor;
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 = restClient.postForObject(UPDATE_SERVER_BASE_URL + "/api/v2/thingsboard/updates", request, UpdateMessage.class);
if (updateMessage.isUpdateAvailable() && !updateMessage.equals(prevUpdateMessage)) {
notificationRuleProcessingService.process(TenantId.SYS_TENANT_ID, NewPlatformVersionTrigger.builder()
notificationRuleProcessor.process(NewPlatformVersionTrigger.builder()
.message(updateMessage)
.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 */
private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
if (subscriptionUpdate.getNotificationUpdate() != null) {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
try {
if (subscriptionUpdate.getNotificationUpdate() != null) {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
} 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) {
log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification();
UUID notificationId = update.getNotificationId();
UUID notificationId = notification != null ? notification.getUuidId() : update.getNotificationId();
if (update.isCreated()) {
subscription.getLatestUnreadNotifications().put(notificationId, notification);
subscription.getTotalUnreadCounter().incrementAndGet();
@ -175,10 +179,14 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
/* Notifications count subscription update handling */
private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
if (subscriptionUpdate.getNotificationUpdate() != null) {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
try {
if (subscriptionUpdate.getNotificationUpdate() != null) {
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
} 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;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
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.NotificationStatus;
@ -32,7 +30,7 @@ import java.util.UUID;
@Builder
public class NotificationUpdate {
private NotificationId notificationId;
private UUID notificationId;
private boolean created;
private Notification notification;
@ -43,10 +41,4 @@ public class NotificationUpdate {
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;
import com.google.common.util.concurrent.Futures;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
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.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
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.NotificationRequestStats;
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.targets.NotificationTarget;
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.NotificationTemplate;
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.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
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) {
NotificationRequestConfig config = new NotificationRequestConfig();
config.setSendingDelayInSec(delayInSec);
UserOriginatedNotificationInfo notificationInfo = new UserOriginatedNotificationInfo();
notificationInfo.setDescription("My description");
NotificationRequest notificationRequest = NotificationRequest.builder()
.targets(targets.stream().map(UUIDBased::getId).collect(Collectors.toList()))
.templateId(notificationTemplateId)
.info(notificationInfo)
.additionalConfig(config)
.build();
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;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.data.Offset;
import org.java_websocket.client.WebSocketClient;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.server.common.data.User;
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.NotificationDeliveryMethod;
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.NotificationRequestStatus;
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.SlackNotificationDeliveryMethodConfig;
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.PlatformUsersNotificationTargetConfig;
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.NotificationTemplate;
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.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationDao;
@ -67,8 +60,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.InstanceOfAssertFactories.type;
@ -341,8 +332,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
target1Config.setUsersFilter(userListFilter);
target1.setConfiguration(target1Config);
target1 = saveNotificationTarget(target1);
List<UserId> recipients = new ArrayList<>();
recipients.add(tenantAdminUserId);
List<String> recipients = new ArrayList<>();
recipients.add(TENANT_ADMIN_EMAIL);
createDifferentCustomer();
loginTenantAdmin();
@ -354,7 +345,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
customerUser.setCustomerId(differentCustomerId);
customerUser.setEmail("other-customer-" + i + "@thingsboard.org");
customerUser = createUser(customerUser, "12345678");
recipients.add(customerUser.getId());
recipients.add(customerUser.getEmail());
}
NotificationTarget target2 = new NotificationTarget();
target2.setName("Other customer users");
@ -378,7 +369,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
webNotificationTemplate.setEnabled(true);
webNotificationTemplate.setBody("Message for WEB: ${recipientEmail}");
webNotificationTemplate.setBody("Message for WEB: ${recipientEmail} ${unknownParam}");
webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}");
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(target2.getName())).isEqualTo(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();
assertThat(processedTemplates.get(NotificationDeliveryMethod.WEB)).asInstanceOf(type(WebDeliveryMethodNotificationTemplate.class))
.satisfies(template -> {
assertThat(template.getBody())
.startsWith("Message for WEB")
.endsWith(requestorEmail);
.endsWith(requestorEmail + " ${unknownParam}");
assertThat(template.getSubject())
.startsWith("Subject for WEB")
.endsWith(requestorEmail);
@ -440,7 +431,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class))
.satisfies(template -> {
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
@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 {
NotificationSettings settings = new NotificationSettings();
SlackNotificationDeliveryMethodConfig slackConfig = new SlackNotificationDeliveryMethodConfig();
@ -540,7 +484,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTemplateConfig config = new NotificationTemplateConfig();
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
slackNotificationTemplate.setEnabled(true);
slackNotificationTemplate.setBody("To Slack :) ${recipientEmail}");
slackNotificationTemplate.setBody("To Slack :)");
config.setDeliveryMethodsTemplates(Map.of(
NotificationDeliveryMethod.SLACK, slackNotificationTemplate
));
@ -553,14 +497,17 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationTarget.setTenantId(tenantId);
notificationTarget.setName(conversationName + " in Slack");
SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig();
targetConfig.setConversation(new SlackConversation(conversationId, conversationName));
targetConfig.setConversation(SlackConversation.builder()
.id(conversationId)
.title(conversationName)
.build());
notificationTarget.setConfiguration(targetConfig);
notificationTarget = saveNotificationTarget(notificationTarget);
NotificationRequest successfulNotificationRequest = submitNotificationRequest(List.of(notificationTarget.getId()), notificationTemplate.getId(), 0);
await().atMost(2, TimeUnit.SECONDS)
.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());
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
public void testNotificationRuleProcessing_entityActionTrigger() throws Exception {
String notificationSubject = "${actionType}: ${entityType} [${entityId}]";
String notificationText = "User: ${originatorUserName}";
String notificationText = "User: ${userEmail}";
NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.GENERAL, notificationSubject, notificationText, NotificationDeliveryMethod.WEB);
NotificationRule notificationRule = new NotificationRule();
@ -132,7 +132,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
notificationRule.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION);
EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig();
triggerConfig.setEntityType(EntityType.DEVICE);
triggerConfig.setEntityTypes(Set.of(EntityType.DEVICE));
triggerConfig.setCreated(true);
triggerConfig.setUpdated(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.queue.TopicPartitionInfo;
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.ToRuleEngineMsg;
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.TbQueueClusterService;
@ -95,4 +95,5 @@ public interface TbClusterService extends TbQueueClusterService {
void pushEdgeSyncResponseToCore(FromEdgeSyncResponse fromEdgeSyncResponse);
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;
DeviceActivityProto deviceActivityMsg = 6;
NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7;
NotificationRuleProcessorMsg notificationRuleProcessorMsg = 8;
}
/* High priority messages with low latency are handled by ThingsBoard Core Service separately */
@ -1053,3 +1054,7 @@ message NotificationSchedulerServiceMsg {
int64 requestIdLSB = 4;
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.Data;
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.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.google.common.util.concurrent.ListenableFuture;
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.AlarmQuery;
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.TenantId;
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.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.PageLink;
@ -41,9 +43,9 @@ public interface NotificationTargetService {
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);

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"),
EMAIL("emailApiState", "Email messages"),
SMS("smsApiState", "SMS messages"),
ALARM("alarmApiState", "Created alarms");
ALARM("alarmApiState", "Alarms");
@Getter
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 {
TRANSPORT_MSG_COUNT(ApiFeature.TRANSPORT, "transportMsgCount", "transportMsgLimit"),
TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"),
STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"),
RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"),
JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"),
EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"),
SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"),
CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit");
TRANSPORT_MSG_COUNT(ApiFeature.TRANSPORT, "transportMsgCount", "transportMsgLimit", "message"),
TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit", "data point"),
STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit", "data point"),
RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit", "Rule Engine execution"),
JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit", "JavaScript execution"),
EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit", "email message"),
SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit", "SMS message"),
CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit", "alarm");
private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT};
private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT};
@ -42,11 +42,14 @@ public enum ApiUsageRecordKey {
private final String apiCountKey;
@Getter
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.apiCountKey = apiCountKey;
this.apiLimitKey = apiLimitKey;
this.unitLabel = unitLabel;
}
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 lombok.Data;
import java.io.Serializable;
@ApiModel
@Data
public class UpdateMessage {
public class UpdateMessage implements Serializable {
@ApiModelProperty(position = 1, value = "'True' if new platform update is available.")
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.NoXss;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@ApiModel
@EqualsAndHashCode(callSuper = true)
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();
}
@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
public String toString() {
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
* limitations under the License.
*/
package org.thingsboard.server.dao.alarm;
package org.thingsboard.server.common.data.alarm;
import lombok.Builder;
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 java.io.Serializable;
import java.util.List;
@Data
public class AlarmApiCallResult {
public class AlarmApiCallResult implements Serializable {
private final boolean successful;
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;
import lombok.Data;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import java.util.Collection;
@ -28,6 +27,6 @@ public class NotificationRequestPreview {
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates;
private int totalRecipientsCount;
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.JsonProperty;
import lombok.Data;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import java.util.Collections;
@ -33,6 +32,7 @@ public class NotificationRequestStats {
private final Map<NotificationDeliveryMethod, AtomicInteger> sent;
private final Map<NotificationDeliveryMethod, Map<String, String>> errors;
private String error;
@JsonIgnore
private final Map<NotificationDeliveryMethod, Set<Object>> processedRecipients;
@ -44,9 +44,11 @@ public class NotificationRequestStats {
@JsonCreator
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.errors = errors;
this.error = error;
this.processedRecipients = Collections.emptyMap();
}
@ -60,13 +62,7 @@ public class NotificationRequestStats {
return;
}
String errorMessage = error.getMessage();
String key;
if (recipient instanceof User) {
key = ((User) recipient).getEmail();
} else {
key = "";
}
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(key, errorMessage);
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getTitle(), errorMessage);
}
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,
ALARM,
DEVICE_INACTIVITY,
DEVICE_ACTIVITY,
ENTITY_ACTION,
ALARM_COMMENT,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT,
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 {
private String action;
private String assigneeEmail;
private String assigneeFirstName;
private String assigneeLastName;
private String assigneeEmail;
private UserId assigneeId;
private String userName;
private String userEmail;
private String userFirstName;
private String userLastName;
private String alarmType;
private UUID alarmId;
@ -59,7 +63,9 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
"assigneeLastName", assigneeLastName,
"assigneeEmail", assigneeEmail,
"assigneeId", assigneeId != null ? assigneeId.toString() : null,
"userName", userName,
"userEmail", userEmail,
"userFirstName", userFirstName,
"userLastName", userLastName,
"alarmType", alarmType,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.name().toLowerCase(),
@ -71,12 +77,12 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
}
@Override
public CustomerId getOriginatorEntityCustomerId() {
public CustomerId getAffectedCustomerId() {
return alarmCustomerId;
}
@Override
public UserId getTargetUserId() {
public UserId getAffectedUserId() {
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 action;
private String userName;
private String userEmail;
private String userFirstName;
private String userLastName;
private String alarmType;
private UUID alarmId;
@ -52,7 +55,9 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI
return mapOf(
"comment", comment,
"action", action,
"userName", userName,
"userEmail", userEmail,
"userFirstName", userFirstName,
"userLastName", userLastName,
"alarmType", alarmType,
"alarmId", alarmId.toString(),
"alarmSeverity", alarmSeverity.name().toLowerCase(),
@ -64,7 +69,7 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI
}
@Override
public CustomerId getOriginatorEntityCustomerId() {
public CustomerId getAffectedCustomerId() {
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
public CustomerId getOriginatorEntityCustomerId() {
public CustomerId getAffectedCustomerId() {
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
@AllArgsConstructor
@Builder
public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificationInfo {
public class DeviceActivityNotificationInfo implements RuleOriginatedNotificationInfo {
private String eventType;
private UUID deviceId;
private String deviceName;
private String deviceLabel;
private String deviceType;
private CustomerId deviceCustomerId;
@Override
public CustomerId getOriginatorEntityCustomerId() {
return deviceCustomerId;
}
@Override
public Map<String, String> getTemplateData() {
return mapOf(
"eventType", eventType,
"deviceId", deviceId.toString(),
"deviceName", deviceName,
"deviceLabel", deviceLabel,
@ -55,6 +52,11 @@ public class DeviceInactivityNotificationInfo implements RuleOriginatedNotificat
);
}
@Override
public CustomerId getAffectedCustomerId() {
return deviceCustomerId;
}
@Override
public EntityId getStateEntityId() {
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
@AllArgsConstructor
@Builder
public class EntitiesLimitNotificationInfo implements NotificationInfo {
public class EntitiesLimitNotificationInfo implements RuleOriginatedNotificationInfo {
private EntityType entityType;
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 String entityName;
private ActionType actionType;
private UUID originatorUserId;
private String originatorUserName;
private CustomerId entityCustomerId;
@Override
public CustomerId getOriginatorEntityCustomerId() {
return entityCustomerId;
}
private UUID userId;
private String userEmail;
private String userFirstName;
private String userLastName;
@Override
public Map<String, String> getTemplateData() {
@ -53,11 +51,18 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
"entityId", entityId.toString(),
"entityName", entityName,
"actionType", actionType.name().toLowerCase(),
"originatorUserId", originatorUserId.toString(),
"originatorUserName", originatorUserName
"userId", userId.toString(),
"userEmail", userEmail,
"userFirstName", userFirstName,
"userLastName", userLastName
);
}
@Override
public CustomerId getAffectedCustomerId() {
return entityCustomerId;
}
@Override
public EntityId getStateEntityId() {
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
@AllArgsConstructor
@Builder
public class NewPlatformVersionNotificationInfo implements NotificationInfo {
public class NewPlatformVersionNotificationInfo implements RuleOriginatedNotificationInfo {
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
@NoArgsConstructor
@Builder
public class RuleEngineComponentLifecycleEventNotificationInfo implements NotificationInfo {
public class RuleEngineComponentLifecycleEventNotificationInfo implements RuleOriginatedNotificationInfo {
private RuleChainId ruleChainId;
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 String msgType;
private Map<String, String> msgMetadata;
private Map<String, String> msgData;
@Override
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("originatorId", msgOriginator.getId().toString());
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;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
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;
}

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 javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.Set;
@Data
@ -38,7 +39,7 @@ public class AlarmNotificationRuleTriggerConfig implements NotificationRuleTrigg
}
@Data
public static class ClearRule {
public static class ClearRule implements Serializable {
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;
import lombok.Data;
import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageStateValue;
import java.util.Set;
import java.util.UUID;
@Data
public class DeviceInactivityNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
public class ApiUsageLimitNotificationRuleTriggerConfig implements NotificationRuleTriggerConfig {
private Set<UUID> devices;
private Set<UUID> deviceProfiles;
// set either devices or profiles
private Set<ApiFeature> apiFeatures;
private Set<ApiUsageStateValue> notifyOn;
@Override
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
* 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 java.util.Collections;
import java.util.Map;
import javax.validation.constraints.NotEmpty;
import java.util.Set;
import java.util.UUID;
@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
public Map<String, String> getTemplateData() {
return Collections.emptyMap();
public NotificationRuleTriggerType getTriggerType() {
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 org.thingsboard.server.common.data.EntityType;
import java.util.Set;
@Data
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 updated;
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")
@JsonSubTypes({
@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 = AlarmCommentNotificationRuleTriggerConfig.class, name = "ALARM_COMMENT"),
@Type(value = RuleEngineComponentLifecycleEventNotificationRuleTriggerConfig.class, name = "RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT"),
@Type(value = AlarmAssignmentNotificationRuleTriggerConfig.class, name = "ALARM_ASSIGNMENT"),
@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 {

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_COMMENT,
DEVICE_INACTIVITY,
DEVICE_ACTIVITY,
ENTITY_ACTION,
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT,
ALARM_ASSIGNMENT,
NEW_PLATFORM_VERSION(false),
ENTITIES_LIMIT(false);
ENTITIES_LIMIT(false),
API_USAGE_LIMIT(false);
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();
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;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import javax.validation.constraints.NotEmpty;
import static org.apache.commons.lang3.StringUtils.isEmpty;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SlackConversation implements NotificationRecipient {
@NotEmpty
private String id;
@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.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotEmpty;
@JsonIgnoreProperties(ignoreUnknown = true)
@ -55,4 +54,8 @@ public abstract class DeliveryMethodNotificationTemplate {
@JsonIgnore
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.NoArgsConstructor;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.NotEmpty;
@ -47,4 +48,9 @@ public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotif
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;
import com.fasterxml.jackson.annotation.JsonIgnore;
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.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.NotEmpty;
import java.util.Optional;
@Data
@NoArgsConstructor
@ -37,7 +42,44 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific
public WebDeliveryMethodNotificationTemplate(WebDeliveryMethodNotificationTemplate other) {
super(other);
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
@ -50,4 +92,10 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific
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
* 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
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.trigger;
package org.thingsboard.server.common.msg.notification.trigger;
import lombok.Builder;
import lombok.Data;
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.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
@Data
@Builder
public class AlarmTrigger implements NotificationRuleTrigger {
private final TenantId tenantId;
private final AlarmApiCallResult alarmUpdate;
@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
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.trigger;
package org.thingsboard.server.common.msg.notification.trigger;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
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.NotificationRuleTrigger;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
@Data
@ -39,7 +38,7 @@ public class EntitiesLimitTrigger implements NotificationRuleTrigger {
@Override
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
* 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.Data;
import org.thingsboard.server.common.data.UpdateMessage;
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
@ -32,6 +33,11 @@ public class NewPlatformVersionTrigger implements NotificationRuleTrigger {
return NotificationRuleTriggerType.NEW_PLATFORM_VERSION;
}
@Override
public TenantId getTenantId() {
return TenantId.SYS_TENANT_ID;
}
@Override
public EntityId getOriginatorEntityId() {
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
* 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.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();
TenantId getTenantId();
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
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.trigger;
package org.thingsboard.server.common.msg.notification.trigger;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
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.plugin.ComponentLifecycleEvent;
@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@Builder
public class RuleEngineComponentLifecycleEventTrigger implements NotificationRuleTrigger {
private final TenantId tenantId;
private final RuleChainId ruleChainId;
private final String ruleChainName;
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
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.trigger;
package org.thingsboard.server.common.msg.notification.trigger;
import lombok.Builder;
import lombok.Data;
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.msg.TbMsg;
import java.util.Map;
@Data
@Builder
public class RuleEngineMsgTrigger implements NotificationRuleTrigger {
private final TenantId tenantId;
private final TbMsg msg;
private final NotificationRuleTriggerType triggerType;
public static Map<String, NotificationRuleTriggerType> msgTypeToTriggerType; // set on init by DefaultNotificationRuleProcessor
@Override
public NotificationRuleTriggerType getType() {
return triggerType;
return msgTypeToTriggerType != null ? msgTypeToTriggerType.get(msg.getType()) : null;
}
@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);
}
@Override
public boolean isMyPartition(ServiceType serviceType, TenantId tenantId, EntityId entityId) {
return resolve(serviceType, tenantId, entityId).isMyPartition();
}
private TopicPartitionInfo resolve(QueueKey queueKey, EntityId entityId) {
int hash = hashFunction.newHasher()
.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);
boolean isMyPartition(ServiceType serviceType, TenantId tenantId, EntityId entityId);
/**
* Received from the Discovery service when network topology is changed.
* @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.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
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) {
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.google.common.util.concurrent.ListenableFuture;
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.AlarmQuery;
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.thingsboard.server.common.data.EntityType;
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.AlarmStatusFilter;
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 org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
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.NotificationRuleCacheValue;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@ -96,6 +99,7 @@ public class DefaultNotificationRuleService extends AbstractCachedEntityService<
.getNotificationRules();
}
@Transactional
@Override
public void deleteNotificationRuleById(TenantId tenantId, NotificationRuleId id) {
NotificationRule notificationRule = findNotificationRuleById(tenantId, id);
@ -106,6 +110,14 @@ public class DefaultNotificationRuleService extends AbstractCachedEntityService<
@Override
public void deleteNotificationRulesByTenantId(TenantId 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

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.thingsboard.common.util.JacksonUtil;
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.EntityType;
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.AlarmNotificationRuleTriggerConfig;
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.EntityActionNotificationRuleTriggerConfig;
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 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 usage: ${currentCount}/${limit} (${percents}%)");
EntitiesLimitNotificationRuleTriggerConfig entitiesLimitRuleTriggerConfig = new EntitiesLimitNotificationRuleTriggerConfig();
entitiesLimitRuleTriggerConfig.setEntityTypes(null);
entitiesLimitRuleTriggerConfig.setThreshold(0.8f);
createRule(tenantId, "Entities 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");
createRule(tenantId, "Entities count limit", entitiesLimitNotificationTemplate.getId(), entitiesLimitRuleTriggerConfig,
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;
}
NotificationTarget originatorEntityOwnerUsers = createTarget(tenantId, "Users of rule trigger entity's owner", new OriginatorEntityOwnerUsersFilter(),
"Customer users in case trigger entity (e.g. alarm) has customer, tenant admins otherwise");
NotificationTarget originatorEntityOwnerUsers = createTarget(tenantId, "Users of the entity owner", new OriginatorEntityOwnerUsersFilter(),
"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(),
"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}",
"Severity: ${alarmSeverity}, originator: ${alarmOriginatorEntityType} '${alarmOriginatorName}'",
"notifications", null, null);
AlarmNotificationRuleTriggerConfig alarmRuleTriggerConfig = new AlarmNotificationRuleTriggerConfig();
alarmRuleTriggerConfig.setAlarmTypes(null);
alarmRuleTriggerConfig.setAlarmSeverities(null);
alarmRuleTriggerConfig.setNotifyOn(Set.of(AlarmAction.CREATED, AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED));
createRule(tenantId, "Alarm", alarmNotificationTemplate.getId(), alarmRuleTriggerConfig,
List.of(originatorEntityOwnerUsers.getId()), "Send notification to tenant admins or customer users " +
"when any alarm is created, updated or cleared");
alarmRuleTriggerConfig.setNotifyOn(Set.of(AlarmAction.SEVERITY_CHANGED, AlarmAction.ACKNOWLEDGED, AlarmAction.CLEARED));
createRule(tenantId, "Alarm update", alarmUpdateNotificationTemplate.getId(), alarmRuleTriggerConfig,
List.of(tenantAdmins.getId(), originatorEntityOwnerUsers.getId()), "Send notification to tenant admins and alarm's customer users " +
"when any alarm is updated or cleared");
NotificationTemplate deviceActionNotificationTemplate = createTemplate(tenantId, "Device action notification", NotificationType.ENTITY_ACTION,
"${entityType} was ${actionType}",
"${entityType} '${entityName}' was ${actionType} by user ${originatorUserName}",
"info", "Go to Device", "/devices/${entityId}");
"${entityType} '${entityName}' was ${actionType} by user ${userEmail}",
"info", "Go to device", "/devices/${entityId}");
EntityActionNotificationRuleTriggerConfig deviceActionRuleTriggerConfig = new EntityActionNotificationRuleTriggerConfig();
deviceActionRuleTriggerConfig.setEntityType(EntityType.DEVICE);
deviceActionRuleTriggerConfig.setEntityTypes(Set.of(EntityType.DEVICE));
deviceActionRuleTriggerConfig.setCreated(true);
deviceActionRuleTriggerConfig.setUpdated(false);
deviceActionRuleTriggerConfig.setDeleted(true);
createRule(tenantId, "Device created or deleted", deviceActionNotificationTemplate.getId(), deviceActionRuleTriggerConfig,
deviceActionRuleTriggerConfig.setDeleted(false);
createRule(tenantId, "Device created", deviceActionNotificationTemplate.getId(), deviceActionRuleTriggerConfig,
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,
"Device '${deviceName}' inactive",
"Device '${deviceName}' with type '${deviceType}' became inactive",
"info", "Go to Device", "/devices/${deviceId}");
DeviceInactivityNotificationRuleTriggerConfig deviceInactivityRuleTriggerConfig = new DeviceInactivityNotificationRuleTriggerConfig();
deviceInactivityRuleTriggerConfig.setDevices(null);
deviceInactivityRuleTriggerConfig.setDeviceProfiles(null);
createRule(tenantId, "Device became inactive", deviceInactivityNotificationTemplate.getId(), deviceInactivityRuleTriggerConfig,
NotificationTemplate deviceActivityNotificationTemplate = createTemplate(tenantId, "Device activity notification", NotificationType.DEVICE_ACTIVITY,
"Device '${deviceName}' became ${eventType}",
"Device '${deviceName}' of type '${deviceType}' is now ${eventType}",
"info", "Go to device", "/devices/${deviceId}");
DeviceActivityNotificationRuleTriggerConfig deviceActivityRuleTriggerConfig = new DeviceActivityNotificationRuleTriggerConfig();
deviceActivityRuleTriggerConfig.setDevices(null);
deviceActivityRuleTriggerConfig.setDeviceProfiles(null);
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 " +
"when any device became inactive");
"when any device changes its activity state");
NotificationTemplate alarmCommentNotificationTemplate = createTemplate(tenantId, "Alarm comment notification", NotificationType.ALARM_COMMENT,
"Comment on '${alarmType}' alarm",
"${userName} ${action} comment: ${comment}",
"${userEmail} ${action} comment: ${comment}",
"people", null, null);
AlarmCommentNotificationRuleTriggerConfig alarmCommentRuleTriggerConfig = new AlarmCommentNotificationRuleTriggerConfig();
alarmCommentRuleTriggerConfig.setAlarmTypes(null);
@ -192,7 +217,7 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
NotificationTemplate alarmAssignedNotificationTemplate = createTemplate(tenantId, "Alarm assigned notification", NotificationType.ALARM_ASSIGNMENT,
"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);
AlarmAssignmentNotificationRuleTriggerConfig alarmAssignmentRuleTriggerConfig = new AlarmAssignmentNotificationRuleTriggerConfig();
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");
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}",
"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();
ruleEngineComponentLifecycleEventRuleTriggerConfig.setRuleChains(null);
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.notification.NotificationRequestStatus;
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.NotificationTargetConfig;
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);
Objects.requireNonNull(notificationTarget, "Notification target [" + targetId + "] not found");
NotificationTargetConfig configuration = notificationTarget.getConfiguration();
return findRecipientsForNotificationTargetConfig(tenantId, customerId, configuration, pageLink);
return findRecipientsForNotificationTargetConfig(tenantId, (PlatformUsersNotificationTargetConfig) configuration, pageLink);
}
@Override
public int countRecipientsForNotificationTargetConfig(TenantId tenantId, NotificationTargetConfig targetConfig) {
return (int) findRecipientsForNotificationTargetConfig(tenantId, null, targetConfig, new PageLink(1)).getTotalElements();
}
@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();
public PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, PlatformUsersNotificationTargetConfig targetConfig, PageLink pageLink) {
UsersFilter usersFilter = targetConfig.getUsersFilter();
switch (usersFilter.getType()) {
case USER_LIST: {
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:
return userService.findSysAdmins(pageLink);
case ALL_USERS: {
@ -153,13 +144,34 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
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()) {
return userService.findCustomerUsers(tenantId, customerId, pageLink);
} else {
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<>();
}

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

@ -32,8 +32,8 @@ public class AdminSettingsDataValidator extends DataValidator<AdminSettings> {
@Override
protected void validateCreate(TenantId tenantId, AdminSettings adminSettings) {
AdminSettings existentAdminSettingsWithKey = adminSettingsService.findAdminSettingsByKey(tenantId, adminSettings.getKey());
if (existentAdminSettingsWithKey != null) {
AdminSettings existingSettings = adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, adminSettings.getKey());
if (existingSettings != null) {
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.AlarmDataQuery;
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.model.ModelConstants;
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
@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,
@Param("status") NotificationStatus status);

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

Loading…
Cancel
Save