Browse Source

Refactoring; TbSlackNode

pull/7511/head
ViacheslavKlimov 4 years ago
parent
commit
0a17f99360
  1. 7
      application/src/main/data/upgrade/3.4.3/schema_update.sql
  2. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  3. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  4. 21
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  5. 22
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java
  6. 13
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java
  7. 18
      application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
  8. 50
      application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java
  9. 134
      application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
  10. 15
      application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java
  11. 11
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  12. 27
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java
  13. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/AlreadySentException.java
  14. 11
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java
  15. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java
  16. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java
  17. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  18. 32
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java
  19. 11
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java
  20. 7
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  21. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java
  22. 16
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java
  23. 7
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java
  24. 57
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  25. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  26. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java
  27. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java
  28. 17
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java
  29. 7
      dao/src/main/resources/sql/schema-entities.sql
  30. 3
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  31. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackConversation.java
  32. 10
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java
  33. 55
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java
  34. 29
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java
  35. 86
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java
  36. 47
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java

7
application/src/main/data/upgrade/3.4.3/schema_update.sql

@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS notification_template (
created_time BIGINT NOT NULL, created_time BIGINT NOT NULL,
tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
notification_type VARCHAR(255) NOT NULL,
configuration VARCHAR(10000) NOT NULL configuration VARCHAR(10000) NOT NULL
); );
@ -37,9 +38,8 @@ CREATE TABLE IF NOT EXISTS notification_rule (
tenant_id UUID NOT NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, tenant_id UUID NOT NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id),
delivery_methods VARCHAR(255), delivery_methods VARCHAR(255) NOT NULL,
initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id), configuration VARCHAR(2000) NOT NULL
escalation_config VARCHAR(500)
); );
CREATE TABLE IF NOT EXISTS notification_request ( CREATE TABLE IF NOT EXISTS notification_request (
@ -47,7 +47,6 @@ CREATE TABLE IF NOT EXISTS notification_request (
created_time BIGINT NOT NULL, created_time BIGINT NOT NULL,
tenant_id UUID NOT NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, tenant_id UUID NOT NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id),
type VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id), template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id),
info VARCHAR(1000), info VARCHAR(1000),
delivery_methods VARCHAR(255), delivery_methods VARCHAR(255),

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

@ -32,6 +32,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.script.api.js.JsInvokeService; import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService; import org.thingsboard.script.api.tbel.TbelInvokeService;
@ -323,6 +324,10 @@ public class ActorSystemContext {
@Getter @Getter
private NotificationManager notificationManager; private NotificationManager notificationManager;
@Autowired
@Getter
private SlackService slackService;
@Lazy @Lazy
@Autowired(required = false) @Autowired(required = false)
@Getter @Getter

6
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -36,6 +36,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.rule.engine.util.TenantIdLoader; import org.thingsboard.rule.engine.util.TenantIdLoader;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
@ -685,6 +686,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getNotificationManager(); return mainCtx.getNotificationManager();
} }
@Override
public SlackService getSlackService() {
return mainCtx.getSlackService();
}
@Override @Override
public RuleEngineRpcService getRpcService() { public RuleEngineRpcService getRpcService() {
return mainCtx.getTbRuleEngineDeviceRpcService(); return mainCtx.getTbRuleEngineDeviceRpcService();

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

@ -17,7 +17,6 @@ package org.thingsboard.server.controller;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@ -30,10 +29,13 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.rule.engine.api.slack.SlackConversation;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationOriginatorType;
@ -44,13 +46,11 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.NotificationManagerHelper;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.slack.SlackConversation;
import org.thingsboard.server.service.slack.SlackService;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -65,7 +65,7 @@ public class NotificationController extends BaseController {
private final NotificationService notificationService; private final NotificationService notificationService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
private final NotificationManager notificationManager; private final NotificationManager notificationManager;
private final NotificationManagerHelper notificationManagerHelper; private final NotificationSettingsService notificationSettingsService;
private final SlackService slackService; private final SlackService slackService;
@GetMapping("/notifications") @GetMapping("/notifications")
@ -102,9 +102,6 @@ public class NotificationController extends BaseController {
notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN); notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN);
notificationRequest.setOriginatorEntityId(user.getId()); notificationRequest.setOriginatorEntityId(user.getId());
if (StringUtils.isBlank(notificationRequest.getType())) {
notificationRequest.setType("General");
}
if (notificationRequest.getInfo() != null && notificationRequest.getInfo().getOriginatorType() != null) { if (notificationRequest.getInfo() != null && notificationRequest.getInfo().getOriginatorType() != null) {
throw new IllegalArgumentException("Unsupported notification info type"); throw new IllegalArgumentException("Unsupported notification info type");
} }
@ -147,20 +144,22 @@ public class NotificationController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationSettings saveNotificationSettings(@RequestBody NotificationSettings notificationSettings, public NotificationSettings saveNotificationSettings(@RequestBody NotificationSettings notificationSettings,
@AuthenticationPrincipal SecurityUser user) { @AuthenticationPrincipal SecurityUser user) {
notificationManagerHelper.saveNotificationSettings(user.getTenantId(), notificationSettings); TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId();
notificationSettingsService.saveNotificationSettings(tenantId, notificationSettings);
return notificationSettings; return notificationSettings;
} }
@GetMapping("/notification/settings") @GetMapping("/notification/settings")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) { public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) {
return notificationManagerHelper.getNotificationSettings(user.getTenantId()); TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId();
return notificationSettingsService.findNotificationSettings(tenantId);
} }
@GetMapping("/notification/slack/conversations") @GetMapping("/notification/slack/conversations")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversation.Type type, public List<SlackConversation> listSlackConversations(@RequestParam SlackConversation.Type type,
@AuthenticationPrincipal SecurityUser user) throws Exception { @AuthenticationPrincipal SecurityUser user) {
NotificationSettings settings = getNotificationSettings(user); NotificationSettings settings = getNotificationSettings(user);
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig) SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK); settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);

22
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationManager.java

@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.NotificationManager; import org.thingsboard.rule.engine.api.NotificationManager;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
@ -36,13 +37,16 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStatus
import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.NotificationsTopicService; import org.thingsboard.server.queue.discovery.NotificationsTopicService;
@ -71,7 +75,8 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
private final NotificationTargetService notificationTargetService; private final NotificationTargetService notificationTargetService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
private final NotificationService notificationService; private final NotificationService notificationService;
private final NotificationManagerHelper notificationManagerHelper; private final NotificationTemplateService notificationTemplateService;
private final NotificationSettingsService notificationSettingsService;
private final DbCallbackExecutorService dbCallbackExecutorService; private final DbCallbackExecutorService dbCallbackExecutorService;
private final NotificationsTopicService notificationsTopicService; private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider; private final TbQueueProducerProvider producerProvider;
@ -82,7 +87,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId()); log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId());
notificationRequest.setTenantId(tenantId); notificationRequest.setTenantId(tenantId);
NotificationSettings settings = notificationManagerHelper.getNotificationSettings(tenantId); NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
notificationRequest.getDeliveryMethods().forEach(deliveryMethod -> { notificationRequest.getDeliveryMethods().forEach(deliveryMethod -> {
if (!settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod) || !settings.getDeliveryMethodsConfigs().get(deliveryMethod).isEnabled()) { if (!settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod) || !settings.getDeliveryMethodsConfigs().get(deliveryMethod).isEnabled()) {
throw new IllegalArgumentException("Delivery method " + deliveryMethod + " is not enabled or configured"); throw new IllegalArgumentException("Delivery method " + deliveryMethod + " is not enabled or configured");
@ -98,6 +103,10 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
return savedNotificationRequest; return savedNotificationRequest;
} }
} }
if (notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), new PageLink(1))
.getTotalElements() == 0) {
throw new IllegalArgumentException("No target recipients");
}
notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); notificationRequest.setStatus(NotificationRequestStatus.PROCESSED);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
@ -108,7 +117,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
.request(savedNotificationRequest) .request(savedNotificationRequest)
.additionalTemplateContext(notificationRequest.getTemplateContext()) .additionalTemplateContext(notificationRequest.getTemplateContext())
.build(); .build();
ctx.init(notificationManagerHelper); ctx.init(notificationTemplateService);
DaoUtil.processBatches(pageLink -> { DaoUtil.processBatches(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink);
@ -129,7 +138,8 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
results.add(resultFuture); results.add(resultFuture);
} }
} }
Futures.whenAllComplete(results).run(() -> {
Futures.allAsList(results).addListener(() -> {
try { try {
notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats()); notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats());
} catch (Exception e) { } catch (Exception e) {
@ -145,7 +155,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
String text; String text;
try { try {
DeliveryMethodNotificationTemplate template = ctx.getTemplate(notificationChannel.getDeliveryMethod()); DeliveryMethodNotificationTemplate template = ctx.getTemplate(notificationChannel.getDeliveryMethod());
text = notificationManagerHelper.processTemplate(template.getBody(), ctx.createTemplateContext(recipient)); text = TbNodeUtils.processTemplate(template.getBody(), ctx.createTemplateContext(recipient));
} catch (Exception e) { } catch (Exception e) {
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);
} }
@ -173,7 +183,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
Notification notification = Notification.builder() Notification notification = Notification.builder()
.requestId(request.getId()) .requestId(request.getId())
.recipientId(recipient.getId()) .recipientId(recipient.getId())
.type(request.getType()) .type(ctx.getNotificationTemplate().getNotificationType())
.text(text) .text(text)
.info(request.getInfo()) .info(request.getInfo())
.originatorType(request.getOriginatorType()) .originatorType(request.getOriginatorType())

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

@ -32,9 +32,9 @@ import org.thingsboard.server.common.data.notification.NotificationInfo;
import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationOriginatorType;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationSeverity;
import org.thingsboard.server.common.data.notification.rule.NonConfirmedNotificationEscalation; import org.thingsboard.server.common.data.notification.rule.NonConfirmedNotificationEscalation;
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -76,6 +76,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
@Override @Override
public ListenableFuture<Void> onNotificationRuleDeleted(TenantId tenantId, NotificationRuleId ruleId) { public ListenableFuture<Void> onNotificationRuleDeleted(TenantId tenantId, NotificationRuleId ruleId) {
return dbCallbackExecutorService.submit(() -> { return dbCallbackExecutorService.submit(() -> {
// FIXME [viacheslav]
// need to remove fk constraint in notificationRequest to rule // need to remove fk constraint in notificationRequest to rule
// todo: do we need to remove all notifications when notification request is deleted? // todo: do we need to remove all notifications when notification request is deleted?
return null; return null;
@ -98,12 +99,13 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
} }
if (notificationRequests.isEmpty()) { if (notificationRequests.isEmpty()) {
NotificationTargetId initialNotificationTargetId = notificationRule.getInitialNotificationTargetId(); NotificationRuleConfig config = notificationRule.getConfiguration();
NotificationTargetId initialNotificationTargetId = config.getInitialNotificationTargetId();
if (initialNotificationTargetId != null) { if (initialNotificationTargetId != null) {
submitNotificationRequest(tenantId, initialNotificationTargetId, notificationRule, alarm, 0); submitNotificationRequest(tenantId, initialNotificationTargetId, notificationRule, alarm, 0);
} }
if (notificationRule.getEscalationConfig() != null) { if (config.getEscalationConfig() != null) {
for (NonConfirmedNotificationEscalation escalation : notificationRule.getEscalationConfig().getEscalations()) { for (NonConfirmedNotificationEscalation escalation : config.getEscalationConfig().getEscalations()) {
submitNotificationRequest(tenantId, escalation.getNotificationTargetId(), notificationRule, alarm, escalation.getDelayInSec()); submitNotificationRequest(tenantId, escalation.getNotificationTargetId(), notificationRule, alarm, escalation.getDelayInSec());
} }
} }
@ -119,7 +121,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
} }
} }
private boolean alarmAcknowledged(Alarm alarm) { // todo: decide when to consider the alarm processed by notification target (not to escalate then) private boolean alarmAcknowledged(Alarm alarm) {
return alarm.getStatus().isAck() && alarm.getStatus().isCleared(); return alarm.getStatus().isAck() && alarm.getStatus().isCleared();
} }
@ -138,7 +140,6 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId) .tenantId(tenantId)
.targetId(targetId) .targetId(targetId)
.type("Alarm")
.templateId(notificationRule.getTemplateId()) .templateId(notificationRule.getTemplateId())
.deliveryMethods(notificationRule.getDeliveryMethods()) .deliveryMethods(notificationRule.getDeliveryMethods())
.additionalConfig(config) .additionalConfig(config)

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

@ -26,9 +26,14 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class NotificationProcessingContext { public class NotificationProcessingContext {
@ -40,6 +45,8 @@ public class NotificationProcessingContext {
private final NotificationRequest request; private final NotificationRequest request;
private final Map<String, String> additionalTemplateContext; private final Map<String, String> additionalTemplateContext;
@Getter
private NotificationTemplate notificationTemplate;
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates; private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates;
@Getter @Getter
private NotificationRequestStats stats; private NotificationRequestStats stats;
@ -52,8 +59,15 @@ public class NotificationProcessingContext {
this.additionalTemplateContext = additionalTemplateContext; this.additionalTemplateContext = additionalTemplateContext;
} }
public void init(NotificationManagerHelper notificationManagerHelper) { public void init(NotificationTemplateService templateService) {
templates = notificationManagerHelper.getTemplates(tenantId, request.getTemplateId(), request.getDeliveryMethods()); notificationTemplate = templateService.findNotificationTemplateById(tenantId, request.getTemplateId());
NotificationTemplateConfig config = notificationTemplate.getConfiguration();
templates = request.getDeliveryMethods().stream()
.collect(Collectors.toMap(k -> k, deliveryMethod -> {
return Optional.ofNullable(config.getTemplates())
.map(templates -> templates.get(deliveryMethod))
.orElse(config.getDefaultTemplate());
}));
stats = new NotificationRequestStats(); stats = new NotificationRequestStats();
} }

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

@ -20,57 +20,53 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.service.slack.SlackService; import org.thingsboard.rule.engine.api.slack.SlackConversation;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.AlreadySentException;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext; import org.thingsboard.server.service.notification.NotificationProcessingContext;
import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings("UnstableApiUsage")
public class SlackNotificationChannel implements NotificationChannel { public class SlackNotificationChannel implements NotificationChannel {
private final SlackService slackService; private final SlackService slackService;
private ExecutorService executor; private final ExternalCallExecutorService executor;
@PostConstruct
private void init() {
executor = Executors.newSingleThreadExecutor();
}
@Override @Override
public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) { public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
SlackDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.SLACK); SlackDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.SLACK);
SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK); SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK);
String conversationId = template.getConversationId(); if (StringUtils.isNotEmpty(template.getConversationId())) { // if conversationId is set, we only need to send message once
if (StringUtils.isNotEmpty(conversationId)) {
if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) { if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) {
// FIXME stats.sent will be reported anyway return Futures.immediateFailedFuture(new AlreadySentException());
return Futures.immediateFuture(null); // if conversationId is set, we only need to send message once } else {
return executor.submit(() -> {
slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), template.getConversationId(), text);
return null;
});
} }
} else { } else {
String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' '); if (StringUtils.isNoneEmpty(recipient.getFirstName(), recipient.getLastName())) {
if (StringUtils.isNotEmpty(username)) { String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' ');
conversationId = username; return executor.submit(() -> {
SlackConversation conversation = slackService.findConversation(recipient.getTenantId(), config.getBotToken(), SlackConversation.Type.USER, username);
if (conversation == null) {
throw new IllegalArgumentException("Slack user not found for given name '" + username + "'");
}
slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), conversation.getId(), text);
return null;
});
} else { } else {
return Futures.immediateFailedFuture(new IllegalArgumentException("Couldn't determine Slack username for the user")); return Futures.immediateFailedFuture(new IllegalArgumentException("Couldn't determine Slack username for the user"));
} }
} }
return send(ctx.getTenantId(), config.getBotToken(), conversationId, text);
}
private ListenableFuture<Void> send(TenantId tenantId, String botToken, String conversationId, String text) {
return Futures.submit(() -> {
slackService.sendMessage(tenantId, botToken, conversationId, text);
return null;
}, executor);
} }
@Override @Override

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

@ -15,87 +15,139 @@
*/ */
package org.thingsboard.server.service.slack; package org.thingsboard.server.service.slack;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.slack.api.Slack; import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient; import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.SlackApiRequest;
import com.slack.api.methods.SlackApiTextResponse; import com.slack.api.methods.SlackApiTextResponse;
import com.slack.api.methods.request.chat.ChatPostMessageRequest; import com.slack.api.methods.request.chat.ChatPostMessageRequest;
import com.slack.api.methods.request.conversations.ConversationsListRequest; import com.slack.api.methods.request.conversations.ConversationsListRequest;
import com.slack.api.methods.request.users.UsersListRequest; import com.slack.api.methods.request.users.UsersListRequest;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
import com.slack.api.methods.response.conversations.ConversationsListResponse; import com.slack.api.methods.response.conversations.ConversationsListResponse;
import com.slack.api.methods.response.users.UsersListResponse; import com.slack.api.methods.response.users.UsersListResponse;
import com.slack.api.model.ConversationType; import com.slack.api.model.ConversationType;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.slack.SlackConversation;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.util.ThrowingBiFunction;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor
public class DefaultSlackService implements SlackService { public class DefaultSlackService implements SlackService {
private final NotificationSettingsService notificationSettingsService;
private final Slack slack = Slack.getInstance(); private final Slack slack = Slack.getInstance();
private final Cache<String, List<SlackConversation>> cache = Caffeine.newBuilder()
.expireAfterWrite(20, TimeUnit.SECONDS)
.maximumSize(100)
.build();
private static final int CONVERSATIONS_LIMIT = 1000;
@Override @Override
public void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception { public void sendMessage(TenantId tenantId, String token, String conversationId, String message) {
ChatPostMessageRequest request = ChatPostMessageRequest.builder() ChatPostMessageRequest request = ChatPostMessageRequest.builder()
.channel(conversationId) .channel(conversationId)
.text(message) .text(message)
.build(); .build();
ChatPostMessageResponse response = slack.methods(token).chatPostMessage(request); sendRequest(token, request, MethodsClient::chatPostMessage);
check(response); }
@Override
public List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) {
return cache.get(conversationType + ":" + token, k -> {
if (conversationType == SlackConversation.Type.USER) {
UsersListRequest request = UsersListRequest.builder()
.limit(CONVERSATIONS_LIMIT)
.build();
UsersListResponse response = sendRequest(token, request, MethodsClient::usersList);
return response.getMembers().stream()
.filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot())
.map(user -> {
SlackConversation conversation = new SlackConversation();
conversation.setId(user.getId());
conversation.setName(String.format("@%s (%s)", user.getName(), user.getRealName()));
return conversation;
})
.collect(Collectors.toList());
} else {
ConversationsListRequest request = ConversationsListRequest.builder()
.types(List.of(conversationType == SlackConversation.Type.PUBLIC_CHANNEL ?
ConversationType.PUBLIC_CHANNEL :
ConversationType.PRIVATE_CHANNEL))
.limit(CONVERSATIONS_LIMIT)
.excludeArchived(true)
.build();
ConversationsListResponse response = sendRequest(token, request, MethodsClient::conversationsList);
return response.getChannels().stream()
.filter(channel -> !channel.isArchived())
.map(channel -> {
SlackConversation conversation = new SlackConversation();
conversation.setId(channel.getId());
conversation.setName("#" + channel.getName());
return conversation;
})
.collect(Collectors.toList());
}
});
} }
@Override @Override
public List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception { public SlackConversation findConversation(TenantId tenantId, String token, SlackConversation.Type conversationType, String namePattern) {
MethodsClient methods = slack.methods(token); List<SlackConversation> conversations = listConversations(tenantId, token, conversationType);
if (conversationType == SlackConversation.Type.USER) { return conversations.stream()
UsersListResponse usersListResponse = methods.usersList(UsersListRequest.builder() .filter(conversation -> StringUtils.containsIgnoreCase(conversation.getName(), namePattern))
.limit(1000) .findFirst().orElse(null);
.build()); }
check(usersListResponse);
return usersListResponse.getMembers().stream() @Override
.filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot()) public String getToken(TenantId tenantId) {
.map(user -> { NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
SlackConversation conversation = new SlackConversation(); SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
conversation.setId(user.getId()); settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
conversation.setName(String.format("@%s (%s)", user.getName(), user.getRealName())); if (slackConfig != null) {
return conversation; return slackConfig.getBotToken();
})
.collect(Collectors.toList());
} else { } else {
ConversationsListResponse conversationsListResponse = methods.conversationsList(ConversationsListRequest.builder() return null;
.types(List.of(conversationType == SlackConversation.Type.PUBLIC_CHANNEL ?
ConversationType.PUBLIC_CHANNEL :
ConversationType.PRIVATE_CHANNEL))
.limit(1000)
.excludeArchived(true)
.build());
check(conversationsListResponse);
return conversationsListResponse.getChannels().stream()
.filter(channel -> !channel.isArchived())
.map(channel -> {
SlackConversation conversation = new SlackConversation();
conversation.setId(channel.getId());
conversation.setName("#" + channel.getName());
return conversation;
})
.collect(Collectors.toList());
} }
} }
private void check(SlackApiTextResponse slackResponse) { private <T extends SlackApiRequest, R extends SlackApiTextResponse> R sendRequest(String token, T request, ThrowingBiFunction<MethodsClient, T, R> method) {
if (!slackResponse.isOk()) { MethodsClient client = slack.methods(token);
String error = slackResponse.getError(); R response;
try {
response = method.apply(client, request);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
if (!response.isOk()) {
String error = response.getError();
if (error == null) { if (error == null) {
error = "unknown error"; error = "unknown error";
} }
if (error.contains("missing_scope")) { if (error.contains("missing_scope")) {
String neededScope = slackResponse.getNeeded(); String neededScope = response.getNeeded();
throw new RuntimeException("Bot token scope '" + neededScope + "' is needed"); throw new RuntimeException("Bot token scope '" + neededScope + "' is needed");
} }
throw new RuntimeException("Failed to send message via Slack: " + error); throw new RuntimeException("Failed to send message via Slack: " + error);
} }
return response;
} }
} }

15
application/src/main/java/org/thingsboard/server/service/ttl/NotificationsCleanUpService.java

@ -20,8 +20,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.notification.NotificationDao;
import org.thingsboard.server.dao.notification.NotificationRequestDao;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
@ -34,8 +32,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.NOTIFICATION_TABLE
@Slf4j @Slf4j
public class NotificationsCleanUpService extends AbstractCleanUpService { public class NotificationsCleanUpService extends AbstractCleanUpService {
private final NotificationDao notificationDao;
private final NotificationRequestDao notificationRequestDao;
private final SqlPartitioningRepository partitioningRepository; private final SqlPartitioningRepository partitioningRepository;
@Value("${sql.ttl.notifications.ttl:2592000}") @Value("${sql.ttl.notifications.ttl:2592000}")
@ -43,13 +39,8 @@ public class NotificationsCleanUpService extends AbstractCleanUpService {
@Value("${sql.notifications.partition_size:168}") @Value("${sql.notifications.partition_size:168}")
private int partitionSizeInHours; private int partitionSizeInHours;
public NotificationsCleanUpService(PartitionService partitionService, public NotificationsCleanUpService(PartitionService partitionService, SqlPartitioningRepository partitioningRepository) {
NotificationDao notificationDao,
NotificationRequestDao notificationRequestDao,
SqlPartitioningRepository partitioningRepository) {
super(partitionService); super(partitionService);
this.notificationDao = notificationDao;
this.notificationRequestDao = notificationRequestDao;
this.partitioningRepository = partitioningRepository; this.partitioningRepository = partitioningRepository;
} }
@ -59,11 +50,7 @@ public class NotificationsCleanUpService extends AbstractCleanUpService {
long expTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec); long expTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec);
long partitionDurationMs = TimeUnit.HOURS.toMillis(partitionSizeInHours); long partitionDurationMs = TimeUnit.HOURS.toMillis(partitionSizeInHours);
if (isSystemTenantPartitionMine()) { if (isSystemTenantPartitionMine()) {
long actualExpTime = partitioningRepository.getLastPartitionEnd(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs);
partitioningRepository.dropPartitionsBefore(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); partitioningRepository.dropPartitionsBefore(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs);
// select distinct request_id for period and manually delete them ?
// sql trigger ? ----
} else { } else {
partitioningRepository.cleanupPartitionsCache(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs); partitioningRepository.cleanupPartitionsCache(NOTIFICATION_TABLE_NAME, expTime, partitionDurationMs);
} }

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

@ -374,7 +374,6 @@ public class NotificationApiTest extends AbstractControllerTest {
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId) .tenantId(tenantId)
.targetId(targetId) .targetId(targetId)
.type("Test")
.templateId(notificationTemplate.getId()) .templateId(notificationTemplate.getId())
.info(notificationInfo) .info(notificationInfo)
.deliveryMethods(deliveryMethods.length > 0 ? List.of(deliveryMethods) : List.of(NotificationDeliveryMethod.WEBSOCKET)) .deliveryMethods(deliveryMethods.length > 0 ? List.of(deliveryMethods) : List.of(NotificationDeliveryMethod.WEBSOCKET))
@ -387,6 +386,7 @@ public class NotificationApiTest extends AbstractControllerTest {
NotificationTemplate notificationTemplate = new NotificationTemplate(); NotificationTemplate notificationTemplate = new NotificationTemplate();
notificationTemplate.setTenantId(tenantId); notificationTemplate.setTenantId(tenantId);
notificationTemplate.setName("Notification template for testing"); notificationTemplate.setName("Notification template for testing");
notificationTemplate.setNotificationType("Just a test");
NotificationTemplateConfig config = new NotificationTemplateConfig(); NotificationTemplateConfig config = new NotificationTemplateConfig();
DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate(); DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate();
defaultTemplate.setBody(text); defaultTemplate.setBody(text);
@ -396,6 +396,7 @@ public class NotificationApiTest extends AbstractControllerTest {
EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate(); EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
emailNotificationTemplate.setSubject("Hello from test"); emailNotificationTemplate.setSubject("Hello from test");
emailNotificationTemplate.setBody(text); emailNotificationTemplate.setBody(text);
emailNotificationTemplate.setMethod(deliveryMethod);
config.setTemplates(Map.of( config.setTemplates(Map.of(
deliveryMethod, emailNotificationTemplate deliveryMethod, emailNotificationTemplate
)); ));
@ -405,14 +406,6 @@ public class NotificationApiTest extends AbstractControllerTest {
return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class); return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class);
} }
private void configureNotificationDeliveryMethods(NotificationDeliveryMethod... deliveryMethods) {
NotificationSettings notificationSettings = new NotificationSettings();
notificationSettings.setDeliveryMethodsConfigs(new HashMap<>());
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
notificationSettings.getDeliveryMethodsConfigs().put(deliveryMethod, new NotificationDeliveryMethodConfig());
}
}
private NotificationRequest findNotificationRequest(NotificationRequestId id) throws Exception { private NotificationRequest findNotificationRequest(NotificationRequestId id) throws Exception {
return doGet("/api/notification/request/" + id, NotificationRequest.class); return doGet("/api/notification/request/" + id, NotificationRequest.class);
} }

27
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationSettingsService.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
public interface NotificationSettingsService {
void saveNotificationSettings(TenantId tenantId, NotificationSettings settings);
NotificationSettings findNotificationSettings(TenantId tenantId);
}

5
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationSeverity.java → common/data/src/main/java/org/thingsboard/server/common/data/notification/AlreadySentException.java

@ -15,8 +15,5 @@
*/ */
package org.thingsboard.server.common.data.notification; package org.thingsboard.server.common.data.notification;
public enum NotificationSeverity { public class AlreadySentException extends RuntimeException {
NORMAL,
CRITICAL,
URGENT // send pop-up error
} }

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

@ -16,12 +16,12 @@
package org.thingsboard.server.common.data.notification; package org.thingsboard.server.common.data.notification;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
@ -39,8 +38,6 @@ import javax.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@NoArgsConstructor @NoArgsConstructor
@ -49,11 +46,9 @@ import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY;
public class NotificationRequest extends BaseData<NotificationRequestId> implements HasTenantId, HasName { public class NotificationRequest extends BaseData<NotificationRequestId> implements HasTenantId, HasName {
private TenantId tenantId; private TenantId tenantId;
@NotNull(message = "Target is not specified") @NotNull
private NotificationTargetId targetId; private NotificationTargetId targetId;
@NoXss
private String type;
@NotNull @NotNull
private NotificationTemplateId templateId; private NotificationTemplateId templateId;
@Valid @Valid
@ -77,7 +72,7 @@ public class NotificationRequest extends BaseData<NotificationRequestId> impleme
@JsonIgnore @JsonIgnore
@Override @Override
public String getName() { public String getName() {
return type; return "To target " + targetId + " via " + StringUtils.join(deliveryMethods, ", ");
} }
} }

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

@ -17,11 +17,12 @@ package org.thingsboard.server.common.data.notification;
import lombok.Data; import lombok.Data;
import java.util.Map; import javax.validation.constraints.Max;
@Data @Data
public class NotificationRequestConfig { public class NotificationRequestConfig {
@Max(value = 604800, message = "cannot be longer than 1 week")
private int sendingDelayInSec; private int sendingDelayInSec;
} }

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

@ -47,6 +47,9 @@ public class NotificationRequestStats {
} }
public void reportError(NotificationDeliveryMethod deliveryMethod, User recipient, Throwable error) { public void reportError(NotificationDeliveryMethod deliveryMethod, User recipient, Throwable error) {
if (error instanceof AlreadySentException) {
return;
}
String errorMessage = error.getMessage(); String errorMessage = error.getMessage();
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage); errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage);
} }

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

@ -45,8 +45,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
@NotEmpty @NotEmpty
private List<NotificationDeliveryMethod> deliveryMethods; private List<NotificationDeliveryMethod> deliveryMethods;
@NotNull @NotNull
private NotificationTargetId initialNotificationTargetId;
@Valid @Valid
private NotificationEscalationConfig escalationConfig; private NotificationRuleConfig configuration;
} }

32
common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleConfig.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.notification.rule;
import lombok.Data;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data
public class NotificationRuleConfig {
@NotNull
private NotificationTargetId initialNotificationTargetId;
@Valid
private NotificationEscalationConfig escalationConfig;
}

11
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java

@ -22,14 +22,23 @@ import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class NotificationTemplate extends BaseData<NotificationTemplateId> implements HasTenantId, HasName { public class NotificationTemplate extends BaseData<NotificationTemplateId> implements HasTenantId, HasName {
private TenantId tenantId; private TenantId tenantId;
@NoXss
@NotNull
private String name; private String name;
@NoXss
@NotNull
private String notificationType;
@Valid
private NotificationTemplateConfig configuration; private NotificationTemplateConfig configuration;
// add notification type (notification reason)
} }

7
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -665,7 +665,6 @@ public class ModelConstants {
public static final String NOTIFICATION_REQUEST_TARGET_ID_PROPERTY = "target_id"; public static final String NOTIFICATION_REQUEST_TARGET_ID_PROPERTY = "target_id";
public static final String NOTIFICATION_REQUEST_TEMPLATE_ID_PROPERTY = "template_id"; public static final String NOTIFICATION_REQUEST_TEMPLATE_ID_PROPERTY = "template_id";
public static final String NOTIFICATION_REQUEST_DELIVERY_METHODS_PROPERTY = "delivery_methods"; public static final String NOTIFICATION_REQUEST_DELIVERY_METHODS_PROPERTY = "delivery_methods";
public static final String NOTIFICATION_REQUEST_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_REQUEST_INFO_PROPERTY = "info"; public static final String NOTIFICATION_REQUEST_INFO_PROPERTY = "info";
public static final String NOTIFICATION_REQUEST_ORIGINATOR_TYPE_PROPERTY = "originator_type"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_TYPE_PROPERTY = "originator_type";
public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_ID_PROPERTY = "originator_entity_id"; public static final String NOTIFICATION_REQUEST_ORIGINATOR_ENTITY_ID_PROPERTY = "originator_entity_id";
@ -678,11 +677,11 @@ public class ModelConstants {
public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule"; public static final String NOTIFICATION_RULE_TABLE_NAME = "notification_rule";
public static final String NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY = "template_id"; public static final String NOTIFICATION_RULE_TEMPLATE_ID_PROPERTY = "template_id";
public static final String NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY = "delivery_methods"; public static final String NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY = "delivery_methods";
public static final String NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY = "initial_notification_target_id"; public static final String NOTIFICATION_RULE_CONFIGURATION_PROPERTY = "configuration";
public static final String NOTIFICATION_RULE_ESCALATION_CONFIG_PROPERTY = "escalation_config";
public static final String NOTIFICATION_TEMPLATE_TABLE_NAME = "notification_template"; public static final String NOTIFICATION_TEMPLATE_TABLE_NAME = "notification_template";
public static final String NOTIFICATION_TEMPLATE_CONFIGURATION = "configuration"; public static final String NOTIFICATION_TEMPLATE_NOTIFICATION_TYPE_PROPERTY = "notification_type";
public static final String NOTIFICATION_TEMPLATE_CONFIGURATION_PROPERTY = "configuration";
protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN};

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java

@ -61,9 +61,6 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
@Column(name = ModelConstants.NOTIFICATION_REQUEST_TARGET_ID_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_REQUEST_TARGET_ID_PROPERTY, nullable = false)
private UUID targetId; private UUID targetId;
@Column(name = ModelConstants.NOTIFICATION_REQUEST_TYPE_PROPERTY, nullable = false)
private String type;
@Column(name = ModelConstants.NOTIFICATION_REQUEST_TEMPLATE_ID_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_REQUEST_TEMPLATE_ID_PROPERTY, nullable = false)
private UUID templateId; private UUID templateId;
@ -107,7 +104,6 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
setCreatedTime(notificationRequest.getCreatedTime()); setCreatedTime(notificationRequest.getCreatedTime());
setTenantId(getUuid(notificationRequest.getTenantId())); setTenantId(getUuid(notificationRequest.getTenantId()));
setTargetId(getUuid(notificationRequest.getTargetId())); setTargetId(getUuid(notificationRequest.getTargetId()));
setType(notificationRequest.getType());
setTemplateId(getUuid(notificationRequest.getTemplateId())); setTemplateId(getUuid(notificationRequest.getTemplateId()));
setInfo(toJson(notificationRequest.getInfo())); setInfo(toJson(notificationRequest.getInfo()));
setDeliveryMethods(StringUtils.join(notificationRequest.getDeliveryMethods(), ',')); setDeliveryMethods(StringUtils.join(notificationRequest.getDeliveryMethods(), ','));
@ -129,7 +125,6 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
notificationRequest.setCreatedTime(createdTime); notificationRequest.setCreatedTime(createdTime);
notificationRequest.setTenantId(createId(tenantId, TenantId::new)); notificationRequest.setTenantId(createId(tenantId, TenantId::new));
notificationRequest.setTargetId(createId(targetId, NotificationTargetId::new)); notificationRequest.setTargetId(createId(targetId, NotificationTargetId::new));
notificationRequest.setType(type);
notificationRequest.setTemplateId(createId(templateId, NotificationTemplateId::new)); notificationRequest.setTemplateId(createId(templateId, NotificationTemplateId::new));
notificationRequest.setInfo(fromJson(info, NotificationInfo.class)); notificationRequest.setInfo(fromJson(info, NotificationInfo.class));
if (deliveryMethods != null) { if (deliveryMethods != null) {

16
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRuleEntity.java

@ -22,12 +22,11 @@ import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.rule.NotificationEscalationConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType; import org.thingsboard.server.dao.util.mapping.JsonStringType;
@ -57,12 +56,9 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
@Column(name = ModelConstants.NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY, nullable = false) @Column(name = ModelConstants.NOTIFICATION_RULE_DELIVERY_METHODS_PROPERTY, nullable = false)
private String deliveryMethods; private String deliveryMethods;
@Column(name = ModelConstants.NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY)
private UUID initialNotificationTargetId;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_RULE_ESCALATION_CONFIG_PROPERTY) @Column(name = ModelConstants.NOTIFICATION_RULE_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode escalationConfig; private JsonNode configuration;
public NotificationRuleEntity() {} public NotificationRuleEntity() {}
@ -73,8 +69,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setName(notificationRule.getName()); setName(notificationRule.getName());
setTemplateId(getUuid(notificationRule.getTemplateId())); setTemplateId(getUuid(notificationRule.getTemplateId()));
setDeliveryMethods(StringUtils.join(notificationRule.getDeliveryMethods(), ',')); setDeliveryMethods(StringUtils.join(notificationRule.getDeliveryMethods(), ','));
setInitialNotificationTargetId(getUuid(notificationRule.getInitialNotificationTargetId())); setConfiguration(toJson(notificationRule.getConfiguration()));
setEscalationConfig(toJson(notificationRule.getEscalationConfig()));
} }
@Override @Override
@ -89,8 +84,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
notificationRule.setDeliveryMethods(Arrays.stream(StringUtils.split(deliveryMethods, ',')) notificationRule.setDeliveryMethods(Arrays.stream(StringUtils.split(deliveryMethods, ','))
.filter(StringUtils::isNotBlank).map(NotificationDeliveryMethod::valueOf).collect(Collectors.toList())); .filter(StringUtils::isNotBlank).map(NotificationDeliveryMethod::valueOf).collect(Collectors.toList()));
} }
notificationRule.setInitialNotificationTargetId(createId(initialNotificationTargetId, NotificationTargetId::new)); notificationRule.setConfiguration(fromJson(configuration, NotificationRuleConfig.class));
notificationRule.setEscalationConfig(fromJson(escalationConfig, NotificationEscalationConfig.class));
return notificationRule; return notificationRule;
} }

7
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTemplateEntity.java

@ -46,8 +46,11 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
@Column(name = ModelConstants.NAME_PROPERTY, nullable = false) @Column(name = ModelConstants.NAME_PROPERTY, nullable = false)
private String name; private String name;
@Column(name = ModelConstants.NOTIFICATION_TEMPLATE_NOTIFICATION_TYPE_PROPERTY, nullable = false)
private String notificationType;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION, nullable = false) @Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration; private JsonNode configuration;
public NotificationTemplateEntity() {} public NotificationTemplateEntity() {}
@ -57,6 +60,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
setCreatedTime(notificationTemplate.getCreatedTime()); setCreatedTime(notificationTemplate.getCreatedTime());
setTenantId(getUuid(notificationTemplate.getTenantId())); setTenantId(getUuid(notificationTemplate.getTenantId()));
setName(notificationTemplate.getName()); setName(notificationTemplate.getName());
setNotificationType(notificationTemplate.getNotificationType());
setConfiguration(toJson(notificationTemplate.getConfiguration())); setConfiguration(toJson(notificationTemplate.getConfiguration()));
} }
@ -67,6 +71,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
notificationTemplate.setCreatedTime(createdTime); notificationTemplate.setCreatedTime(createdTime);
notificationTemplate.setTenantId(createId(tenantId, TenantId::fromUUID)); notificationTemplate.setTenantId(createId(tenantId, TenantId::fromUUID));
notificationTemplate.setName(name); notificationTemplate.setName(name);
notificationTemplate.setNotificationType(notificationType);
notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class)); notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class));
return notificationTemplate; return notificationTemplate;
} }

57
application/src/main/java/org/thingsboard/server/service/notification/NotificationManagerHelper.java → dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java

@ -13,53 +13,44 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.notification; package org.thingsboard.server.dao.notification;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
@Component @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class NotificationManagerHelper { public class DefaultNotificationSettingsService implements NotificationSettingsService {
public static final String SETTINGS_KEY = "notifications";
private final NotificationTemplateService templateService;
private final AdminSettingsService adminSettingsService; private final AdminSettingsService adminSettingsService;
public Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> getTemplates(TenantId tenantId, NotificationTemplateId templateId, List<NotificationDeliveryMethod> deliveryMethods) { private static final String SETTINGS_KEY = "notifications";
NotificationTemplate notificationTemplate = templateService.findNotificationTemplateById(tenantId, templateId);
NotificationTemplateConfig config = notificationTemplate.getConfiguration();
return deliveryMethods.stream()
.collect(Collectors.toMap(k -> k, deliveryMethod -> {
return Optional.ofNullable(config.getTemplates())
.map(templates -> templates.get(deliveryMethod))
.orElse(config.getDefaultTemplate());
}));
}
public String processTemplate(String template, Map<String, String> templateContext) { @Override
return TbNodeUtils.processTemplate(template, templateContext); public void saveNotificationSettings(TenantId tenantId, NotificationSettings settings) {
AdminSettings adminSettings = Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY))
.orElseGet(() -> {
AdminSettings newAdminSettings = new AdminSettings();
newAdminSettings.setTenantId(tenantId);
newAdminSettings.setKey(SETTINGS_KEY);
return newAdminSettings;
});
adminSettings.setJsonValue(JacksonUtil.valueToTree(settings));
adminSettingsService.saveAdminSettings(tenantId, adminSettings);
} }
public NotificationSettings getNotificationSettings(TenantId tenantId) { @Override
public NotificationSettings findNotificationSettings(TenantId tenantId) {
return Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY)) return Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY))
.map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), NotificationSettings.class)) .map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), NotificationSettings.class))
.orElseGet(() -> { .orElseGet(() -> {
@ -75,16 +66,4 @@ public class NotificationManagerHelper {
}); });
} }
public void saveNotificationSettings(TenantId tenantId, NotificationSettings notificationSettings) {
AdminSettings adminSettings = Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY))
.orElseGet(() -> {
AdminSettings newAdminSettings = new AdminSettings();
newAdminSettings.setKey(SETTINGS_KEY);
newAdminSettings.setTenantId(tenantId);
return newAdminSettings;
});
adminSettings.setJsonValue(JacksonUtil.valueToTree(notificationSettings));
adminSettingsService.saveAdminSettings(tenantId, adminSettings);
}
} }

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

@ -34,6 +34,7 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -64,6 +65,7 @@ public class DefaultNotificationTargetService implements NotificationTargetServi
@Override @Override
public PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId, PageLink pageLink) { public PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId, PageLink pageLink) {
NotificationTarget notificationTarget = findNotificationTargetById(tenantId, notificationTargetId); NotificationTarget notificationTarget = findNotificationTargetById(tenantId, notificationTargetId);
Objects.requireNonNull(notificationTarget, "Notification target [" + notificationTargetId + "] not found");
NotificationTargetConfig configuration = notificationTarget.getConfiguration(); NotificationTargetConfig configuration = notificationTarget.getConfiguration();
return findRecipientsForNotificationTargetConfig(tenantId, configuration, pageLink); return findRecipientsForNotificationTargetConfig(tenantId, configuration, pageLink);
} }
@ -103,7 +105,6 @@ public class DefaultNotificationTargetService implements NotificationTargetServi
@Override @Override
public void deleteNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId) { public void deleteNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId) {
notificationTargetDao.removeById(tenantId, notificationTargetId.getId()); notificationTargetDao.removeById(tenantId, notificationTargetId.getId());
// todo: delete related notification requests (?)
} }
private static class NotificationTargetValidator extends DataValidator<NotificationTarget> { private static class NotificationTargetValidator extends DataValidator<NotificationTarget> {

4
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.dao.sql.notification; package org.thingsboard.server.dao.sql.notification;
import com.google.common.base.Strings;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -48,8 +47,7 @@ public class JpaNotificationRequestDao extends JpaAbstractDao<NotificationReques
@Override @Override
public PageData<NotificationRequest> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { public PageData<NotificationRequest> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRequestRepository.findByTenantIdAndSearchText(tenantId.getId(), return DaoUtil.toPageData(notificationRequestRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink)));
} }
@Override @Override

5
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java

@ -34,10 +34,7 @@ import java.util.UUID;
@Repository @Repository
public interface NotificationRequestRepository extends JpaRepository<NotificationRequestEntity, UUID> { public interface NotificationRequestRepository extends JpaRepository<NotificationRequestEntity, UUID> {
@Query("SELECT r FROM NotificationRequestEntity r WHERE r.tenantId = :tenantId AND " + Page<NotificationRequestEntity> findByTenantId(UUID tenantId, Pageable pageable);
"(lower(r.type) LIKE lower(concat('%', :searchText, '%')))")
Page<NotificationRequestEntity> findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId,
@Param("searchText") String searchText, Pageable pageable);
List<NotificationRequestEntity> findAllByRuleIdAndOriginatorEntityTypeAndOriginatorEntityId(UUID ruleId, EntityType originatorEntityType, UUID originatorEntityId); List<NotificationRequestEntity> findAllByRuleIdAndOriginatorEntityTypeAndOriginatorEntityId(UUID ruleId, EntityType originatorEntityType, UUID originatorEntityId);

17
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java

@ -57,7 +57,7 @@ public class SqlPartitioningRepository {
long partitionStartTs = calculatePartitionStartTime(entityTs, partitionDurationMs); long partitionStartTs = calculatePartitionStartTime(entityTs, partitionDurationMs);
Map<Long, SqlPartition> partitions = tablesPartitions.computeIfAbsent(table, t -> new ConcurrentHashMap<>()); Map<Long, SqlPartition> partitions = tablesPartitions.computeIfAbsent(table, t -> new ConcurrentHashMap<>());
if (!partitions.containsKey(partitionStartTs)) { if (!partitions.containsKey(partitionStartTs)) {
SqlPartition partition = new SqlPartition(table, partitionStartTs, partitionStartTs + partitionDurationMs, Long.toString(partitionStartTs)); SqlPartition partition = new SqlPartition(table, partitionStartTs, getPartitionEndTime(partitionStartTs, partitionDurationMs), Long.toString(partitionStartTs));
partitionCreationLock.lock(); partitionCreationLock.lock();
try { try {
if (partitions.containsKey(partitionStartTs)) return; if (partitions.containsKey(partitionStartTs)) return;
@ -82,7 +82,7 @@ public class SqlPartitioningRepository {
public void dropPartitionsBefore(String table, long ts, long partitionDurationMs) { public void dropPartitionsBefore(String table, long ts, long partitionDurationMs) {
List<Long> partitions = fetchPartitions(table); List<Long> partitions = fetchPartitions(table);
for (Long partitionStartTime : partitions) { for (Long partitionStartTime : partitions) {
long partitionEndTime = partitionStartTime + partitionDurationMs; long partitionEndTime = getPartitionEndTime(partitionStartTime, partitionDurationMs);
if (partitionEndTime < ts) { if (partitionEndTime < ts) {
log.info("[{}] Detaching expired partition: [{}-{}]", table, partitionStartTime, partitionEndTime); log.info("[{}] Detaching expired partition: [{}-{}]", table, partitionStartTime, partitionEndTime);
boolean success = detachAndDropPartition(table, partitionStartTime); boolean success = detachAndDropPartition(table, partitionStartTime);
@ -95,17 +95,10 @@ public class SqlPartitioningRepository {
} }
} }
public long getLastPartitionEnd(String table, long before, long partitionDurationMs) {
return fetchPartitions(table).stream()
.mapToLong(partitionStartTime -> partitionStartTime + partitionDurationMs)
.filter(partitionEndTime -> partitionEndTime < before)
.max().orElse(0);
}
public void cleanupPartitionsCache(String table, long expTime, long partitionDurationMs) { public void cleanupPartitionsCache(String table, long expTime, long partitionDurationMs) {
Map<Long, SqlPartition> partitions = tablesPartitions.get(table); Map<Long, SqlPartition> partitions = tablesPartitions.get(table);
if (partitions == null) return; if (partitions == null) return;
partitions.keySet().removeIf(startTime -> (startTime + partitionDurationMs) < expTime); partitions.keySet().removeIf(startTime -> getPartitionEndTime(startTime, partitionDurationMs) < expTime);
} }
private boolean detachAndDropPartition(String table, long partitionTs) { private boolean detachAndDropPartition(String table, long partitionTs) {
@ -129,6 +122,10 @@ public class SqlPartitioningRepository {
return false; return false;
} }
private static long getPartitionEndTime(long startTime, long partitionDurationMs) {
return startTime + partitionDurationMs;
}
public List<Long> fetchPartitions(String table) { public List<Long> fetchPartitions(String table) {
List<Long> partitions = new ArrayList<>(); List<Long> partitions = new ArrayList<>();
List<String> partitionsTables = jdbcTemplate.queryForList(SELECT_PARTITIONS_STMT, String.class, table); List<String> partitionsTables = jdbcTemplate.queryForList(SELECT_PARTITIONS_STMT, String.class, table);

7
dao/src/main/resources/sql/schema-entities.sql

@ -793,6 +793,7 @@ CREATE TABLE IF NOT EXISTS notification_template (
created_time BIGINT NOT NULL, created_time BIGINT NOT NULL,
tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
notification_type VARCHAR(255) NOT NULL,
configuration VARCHAR(10000) NOT NULL configuration VARCHAR(10000) NOT NULL
); );
@ -802,9 +803,8 @@ CREATE TABLE IF NOT EXISTS notification_rule (
tenant_id UUID NOT NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, tenant_id UUID NOT NULL CONSTRAINT fk_notification_rule_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id),
delivery_methods VARCHAR(255), delivery_methods VARCHAR(255) NOT NULL,
initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id), configuration VARCHAR(2000) NOT NULL
escalation_config VARCHAR(500)
); );
CREATE TABLE IF NOT EXISTS notification_request ( CREATE TABLE IF NOT EXISTS notification_request (
@ -812,7 +812,6 @@ CREATE TABLE IF NOT EXISTS notification_request (
created_time BIGINT NOT NULL, created_time BIGINT NOT NULL,
tenant_id UUID NOT NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, tenant_id UUID NOT NULL CONSTRAINT fk_notification_request_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id), target_id UUID NOT NULL CONSTRAINT fk_notification_request_target_id REFERENCES notification_target(id),
type VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id), template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id),
info VARCHAR(1000), info VARCHAR(1000),
delivery_methods VARCHAR(255), delivery_methods VARCHAR(255),

3
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.api;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
@ -279,6 +280,8 @@ public interface TbContext {
NotificationManager getNotificationManager(); NotificationManager getNotificationManager();
SlackService getSlackService();
/** /**
* Creates JS Script Engine * Creates JS Script Engine
* @deprecated * @deprecated

2
application/src/main/java/org/thingsboard/server/service/slack/SlackConversation.java → rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackConversation.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.slack; package org.thingsboard.rule.engine.api.slack;
import lombok.Data; import lombok.Data;

10
application/src/main/java/org/thingsboard/server/service/slack/SlackService.java → rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.slack; package org.thingsboard.rule.engine.api.slack;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -21,8 +21,12 @@ import java.util.List;
public interface SlackService { public interface SlackService {
void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception; void sendMessage(TenantId tenantId, String token, String conversationId, String message);
List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception; List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType);
SlackConversation findConversation(TenantId tenantId, String token, SlackConversation.Type conversationType, String namePattern);
String getToken(TenantId tenantId);
} }

55
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java

@ -15,14 +15,13 @@
*/ */
package org.thingsboard.rule.engine.notification; package org.thingsboard.rule.engine.notification;
import org.apache.commons.lang3.StringUtils; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.NotificationOriginatorType; import org.thingsboard.server.common.data.notification.NotificationOriginatorType;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
@ -31,10 +30,8 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static org.thingsboard.common.util.DonAsynchron.withCallback;
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.EXTERNAL,
name = "send notification", name = "send notification",
configClazz = TbNotificationNodeConfiguration.class, configClazz = TbNotificationNodeConfiguration.class,
nodeDescription = "Sends notification to a target", nodeDescription = "Sends notification to a target",
@ -48,39 +45,31 @@ public class TbNotificationNode implements TbNode {
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbNotificationNodeConfiguration.class); this.config = TbNodeUtils.convert(configuration, TbNotificationNodeConfiguration.class);
validateConfig(config);
} }
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
// NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
// .tenantId(ctx.getTenantId()) .tenantId(ctx.getTenantId())
// .targetId(new NotificationTargetId(config.getTargetId())) .targetId(config.getTargetId())
// .type(config.getNotificationReason()) .templateId(config.getTemplateId())
// .textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg)) .deliveryMethods(config.getDeliveryMethods())
// .notificationSeverity(config.getNotificationSeverity()) .originatorType(NotificationOriginatorType.RULE_NODE)
// .originatorType(NotificationOriginatorType.RULE_NODE) .originatorEntityId(ctx.getSelfId())
// .originatorEntityId(ctx.getTenantId()) .build();
// .build(); notificationRequest.setTemplateContext(msg.getMetaData().getData());
// withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> {
// return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest);
// }),
// r -> {
// TbMsgMetaData msgMetaData = msg.getMetaData().copy();
// msgMetaData.putValue("notificationRequestId", r.getUuidId().toString());
// msgMetaData.putValue("notificationTextTemplate", r.getTextTemplate());
// ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData));
// },
// e -> ctx.tellFailure(msg, e));
}
private void validateConfig(TbNotificationNodeConfiguration config) throws TbNodeException { DonAsynchron.withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> {
if (config.getTargetId() == null) { return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest);
throw new TbNodeException("Notification target is not specified"); }),
} r -> {
if (StringUtils.isBlank(config.getNotificationTextTemplate())) { TbMsgMetaData msgMetaData = msg.getMetaData().copy();
throw new TbNodeException("Notification text template is missing"); msgMetaData.putValue("notificationRequestId", r.getUuidId().toString());
} ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData));
},
e -> {
ctx.tellFailure(msg, e);
});
} }
} }

29
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java

@ -17,24 +17,35 @@ package org.thingsboard.rule.engine.notification;
import lombok.Data; import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Data @Data
public class TbNotificationNodeConfiguration implements NodeConfiguration<TbNotificationNodeConfiguration> { public class TbNotificationNodeConfiguration implements NodeConfiguration<TbNotificationNodeConfiguration> {
private UUID targetId; @NotNull
private String notificationReason; private NotificationTargetId targetId;
private String notificationTextTemplate; @NotNull
private NotificationSeverity notificationSeverity; private NotificationTemplateId templateId;
@NotEmpty
private List<NotificationDeliveryMethod> deliveryMethods;
private NotificationRequestConfig additionalConfig;
@Override @Override
public TbNotificationNodeConfiguration defaultConfiguration() { public TbNotificationNodeConfiguration defaultConfiguration() {
TbNotificationNodeConfiguration configuration = new TbNotificationNodeConfiguration(); TbNotificationNodeConfiguration config = new TbNotificationNodeConfiguration();
configuration.setNotificationReason("General"); config.setTargetId(new NotificationTargetId(UUID.randomUUID()));
configuration.setNotificationSeverity(NotificationSeverity.NORMAL); config.setTemplateId(new NotificationTemplateId(UUID.randomUUID()));
return configuration; config.setDeliveryMethods(List.of(NotificationDeliveryMethod.WEBSOCKET));
config.setAdditionalConfig(new NotificationRequestConfig());
return config;
} }
} }

86
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java

@ -0,0 +1,86 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.notification;
import com.google.common.util.concurrent.ListenableFuture;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.slack.SlackConversation;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.concurrent.ExecutionException;
@RuleNode(
type = ComponentType.EXTERNAL,
name = "send to Slack",
configClazz = TbSlackNodeConfiguration.class,
nodeDescription = "Send message to a Slack channel or user",
nodeDetails = "",
uiResources = {"static/rulenode/rulenode-core-config.js"}
)
public class TbSlackNode implements TbNode {
private TbSlackNodeConfiguration config;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbSlackNodeConfiguration.class);
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
String token;
if (config.isUseDefaultNotificationSettings()) {
token = ctx.getSlackService().getToken(ctx.getTenantId());
} else {
token = config.getBotToken();
}
if (token == null) {
throw new IllegalArgumentException("Slack token is missing");
}
String message = TbNodeUtils.processPattern(config.getMessageTemplate(), msg);
ListenableFuture<?> result;
if (StringUtils.isNotEmpty(config.getConversationId())) {
result = ctx.getExternalCallExecutor().executeAsync(() -> {
ctx.getSlackService().sendMessage(ctx.getTenantId(), token, config.getConversationId(), message);
});
} else {
result = ctx.getExternalCallExecutor().executeAsync(() -> {
SlackConversation conversation = ctx.getSlackService().findConversation(ctx.getTenantId(), token, config.getConversationType(), config.getConversationNamePattern());
if (conversation == null) {
throw new IllegalArgumentException("Couldn't find conversation by name pattern");
}
ctx.getSlackService().sendMessage(ctx.getTenantId(), token, conversation.getId(), message);
});
}
DonAsynchron.withCallback(result, r -> {
ctx.tellSuccess(msg);
},
e -> {
ctx.tellFailure(msg, e);
});
}
}

47
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNodeConfiguration.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.notification;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.api.slack.SlackConversation;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
public class TbSlackNodeConfiguration implements NodeConfiguration<TbSlackNodeConfiguration> {
private String botToken;
private boolean useDefaultNotificationSettings;
@NotEmpty
private String messageTemplate;
@NotNull
private SlackConversation.Type conversationType;
private String conversationId; // if not set, need to specify conversationNamePattern
private String conversationNamePattern;
@Override
public TbSlackNodeConfiguration defaultConfiguration() {
TbSlackNodeConfiguration config = new TbSlackNodeConfiguration();
config.setBotToken("xoxb-");
config.setMessageTemplate("Device ${deviceId}: temperature is $[temperature]");
config.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL);
return config;
}
}
Loading…
Cancel
Save