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,
tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
notification_type VARCHAR(255) 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,
name VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id),
delivery_methods VARCHAR(255),
initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id),
escalation_config VARCHAR(500)
delivery_methods VARCHAR(255) NOT NULL,
configuration VARCHAR(2000) NOT NULL
);
CREATE TABLE IF NOT EXISTS notification_request (
@ -47,7 +47,6 @@ CREATE TABLE IF NOT EXISTS notification_request (
created_time BIGINT NOT NULL,
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),
type VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id),
info VARCHAR(1000),
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.NotificationManager;
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.script.api.js.JsInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
@ -323,6 +324,10 @@ public class ActorSystemContext {
@Getter
private NotificationManager notificationManager;
@Autowired
@Getter
private SlackService slackService;
@Lazy
@Autowired(required = false)
@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.TbContext;
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.util.TenantIdLoader;
import org.thingsboard.server.actors.ActorSystemContext;
@ -685,6 +686,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getNotificationManager();
}
@Override
public SlackService getSlackService() {
return mainCtx.getSlackService();
}
@Override
public RuleEngineRpcService getRpcService() {
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.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
@ -30,10 +29,13 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
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.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationId;
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.NotificationDeliveryMethod;
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.dao.notification.NotificationRequestService;
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.service.notification.NotificationManagerHelper;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
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.UUID;
@ -65,7 +65,7 @@ public class NotificationController extends BaseController {
private final NotificationService notificationService;
private final NotificationRequestService notificationRequestService;
private final NotificationManager notificationManager;
private final NotificationManagerHelper notificationManagerHelper;
private final NotificationSettingsService notificationSettingsService;
private final SlackService slackService;
@GetMapping("/notifications")
@ -102,9 +102,6 @@ public class NotificationController extends BaseController {
notificationRequest.setOriginatorType(NotificationOriginatorType.ADMIN);
notificationRequest.setOriginatorEntityId(user.getId());
if (StringUtils.isBlank(notificationRequest.getType())) {
notificationRequest.setType("General");
}
if (notificationRequest.getInfo() != null && notificationRequest.getInfo().getOriginatorType() != null) {
throw new IllegalArgumentException("Unsupported notification info type");
}
@ -147,20 +144,22 @@ public class NotificationController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationSettings saveNotificationSettings(@RequestBody NotificationSettings notificationSettings,
@AuthenticationPrincipal SecurityUser user) {
notificationManagerHelper.saveNotificationSettings(user.getTenantId(), notificationSettings);
TenantId tenantId = user.isSystemAdmin() ? TenantId.SYS_TENANT_ID : user.getTenantId();
notificationSettingsService.saveNotificationSettings(tenantId, notificationSettings);
return notificationSettings;
}
@GetMapping("/notification/settings")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
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")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversation.Type type,
@AuthenticationPrincipal SecurityUser user) throws Exception {
@AuthenticationPrincipal SecurityUser user) {
NotificationSettings settings = getNotificationSettings(user);
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
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.thingsboard.common.util.DonAsynchron;
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.id.NotificationId;
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.settings.NotificationSettings;
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.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationRequestService;
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.NotificationTemplateService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.NotificationsTopicService;
@ -71,7 +75,8 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
private final NotificationTargetService notificationTargetService;
private final NotificationRequestService notificationRequestService;
private final NotificationService notificationService;
private final NotificationManagerHelper notificationManagerHelper;
private final NotificationTemplateService notificationTemplateService;
private final NotificationSettingsService notificationSettingsService;
private final DbCallbackExecutorService dbCallbackExecutorService;
private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider;
@ -82,7 +87,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
log.debug("Processing notification request (tenant id: {}, notification target id: {})", tenantId, notificationRequest.getTargetId());
notificationRequest.setTenantId(tenantId);
NotificationSettings settings = notificationManagerHelper.getNotificationSettings(tenantId);
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
notificationRequest.getDeliveryMethods().forEach(deliveryMethod -> {
if (!settings.getDeliveryMethodsConfigs().containsKey(deliveryMethod) || !settings.getDeliveryMethodsConfigs().get(deliveryMethod).isEnabled()) {
throw new IllegalArgumentException("Delivery method " + deliveryMethod + " is not enabled or configured");
@ -98,6 +103,10 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
return savedNotificationRequest;
}
}
if (notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), new PageLink(1))
.getTotalElements() == 0) {
throw new IllegalArgumentException("No target recipients");
}
notificationRequest.setStatus(NotificationRequestStatus.PROCESSED);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
@ -108,7 +117,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
.request(savedNotificationRequest)
.additionalTemplateContext(notificationRequest.getTemplateContext())
.build();
ctx.init(notificationManagerHelper);
ctx.init(notificationTemplateService);
DaoUtil.processBatches(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink);
@ -129,7 +138,8 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
results.add(resultFuture);
}
}
Futures.whenAllComplete(results).run(() -> {
Futures.allAsList(results).addListener(() -> {
try {
notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats());
} catch (Exception e) {
@ -145,7 +155,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
String text;
try {
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) {
return Futures.immediateFailedFuture(e);
}
@ -173,7 +183,7 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
Notification notification = Notification.builder()
.requestId(request.getId())
.recipientId(recipient.getId())
.type(request.getType())
.type(ctx.getNotificationTemplate().getNotificationType())
.text(text)
.info(request.getInfo())
.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.NotificationRequest;
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.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleConfig;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -76,6 +76,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
@Override
public ListenableFuture<Void> onNotificationRuleDeleted(TenantId tenantId, NotificationRuleId ruleId) {
return dbCallbackExecutorService.submit(() -> {
// FIXME [viacheslav]
// need to remove fk constraint in notificationRequest to rule
// todo: do we need to remove all notifications when notification request is deleted?
return null;
@ -98,12 +99,13 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
}
if (notificationRequests.isEmpty()) {
NotificationTargetId initialNotificationTargetId = notificationRule.getInitialNotificationTargetId();
NotificationRuleConfig config = notificationRule.getConfiguration();
NotificationTargetId initialNotificationTargetId = config.getInitialNotificationTargetId();
if (initialNotificationTargetId != null) {
submitNotificationRequest(tenantId, initialNotificationTargetId, notificationRule, alarm, 0);
}
if (notificationRule.getEscalationConfig() != null) {
for (NonConfirmedNotificationEscalation escalation : notificationRule.getEscalationConfig().getEscalations()) {
if (config.getEscalationConfig() != null) {
for (NonConfirmedNotificationEscalation escalation : config.getEscalationConfig().getEscalations()) {
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();
}
@ -138,7 +140,6 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId)
.targetId(targetId)
.type("Alarm")
.templateId(notificationRule.getTemplateId())
.deliveryMethods(notificationRule.getDeliveryMethods())
.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.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 java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@SuppressWarnings("unchecked")
public class NotificationProcessingContext {
@ -40,6 +45,8 @@ public class NotificationProcessingContext {
private final NotificationRequest request;
private final Map<String, String> additionalTemplateContext;
@Getter
private NotificationTemplate notificationTemplate;
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates;
@Getter
private NotificationRequestStats stats;
@ -52,8 +59,15 @@ public class NotificationProcessingContext {
this.additionalTemplateContext = additionalTemplateContext;
}
public void init(NotificationManagerHelper notificationManagerHelper) {
templates = notificationManagerHelper.getTemplates(tenantId, request.getTemplateId(), request.getDeliveryMethods());
public void init(NotificationTemplateService templateService) {
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();
}

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 org.apache.commons.lang3.StringUtils;
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.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.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Component
@RequiredArgsConstructor
@SuppressWarnings("UnstableApiUsage")
public class SlackNotificationChannel implements NotificationChannel {
private final SlackService slackService;
private ExecutorService executor;
@PostConstruct
private void init() {
executor = Executors.newSingleThreadExecutor();
}
private final ExternalCallExecutorService executor;
@Override
public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
SlackDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.SLACK);
SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK);
String conversationId = template.getConversationId();
if (StringUtils.isNotEmpty(conversationId)) {
if (StringUtils.isNotEmpty(template.getConversationId())) { // if conversationId is set, we only need to send message once
if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) {
// FIXME stats.sent will be reported anyway
return Futures.immediateFuture(null); // if conversationId is set, we only need to send message once
return Futures.immediateFailedFuture(new AlreadySentException());
} else {
return executor.submit(() -> {
slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), template.getConversationId(), text);
return null;
});
}
} else {
String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' ');
if (StringUtils.isNotEmpty(username)) {
conversationId = username;
if (StringUtils.isNoneEmpty(recipient.getFirstName(), recipient.getLastName())) {
String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' ');
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 {
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

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

@ -15,87 +15,139 @@
*/
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.methods.MethodsClient;
import com.slack.api.methods.SlackApiRequest;
import com.slack.api.methods.SlackApiTextResponse;
import com.slack.api.methods.request.chat.ChatPostMessageRequest;
import com.slack.api.methods.request.conversations.ConversationsListRequest;
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.users.UsersListResponse;
import com.slack.api.model.ConversationType;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
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.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.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class DefaultSlackService implements SlackService {
private final NotificationSettingsService notificationSettingsService;
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
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()
.channel(conversationId)
.text(message)
.build();
ChatPostMessageResponse response = slack.methods(token).chatPostMessage(request);
check(response);
sendRequest(token, request, MethodsClient::chatPostMessage);
}
@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
public List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception {
MethodsClient methods = slack.methods(token);
if (conversationType == SlackConversation.Type.USER) {
UsersListResponse usersListResponse = methods.usersList(UsersListRequest.builder()
.limit(1000)
.build());
check(usersListResponse);
return usersListResponse.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());
public SlackConversation findConversation(TenantId tenantId, String token, SlackConversation.Type conversationType, String namePattern) {
List<SlackConversation> conversations = listConversations(tenantId, token, conversationType);
return conversations.stream()
.filter(conversation -> StringUtils.containsIgnoreCase(conversation.getName(), namePattern))
.findFirst().orElse(null);
}
@Override
public String getToken(TenantId tenantId) {
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
if (slackConfig != null) {
return slackConfig.getBotToken();
} else {
ConversationsListResponse conversationsListResponse = methods.conversationsList(ConversationsListRequest.builder()
.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());
return null;
}
}
private void check(SlackApiTextResponse slackResponse) {
if (!slackResponse.isOk()) {
String error = slackResponse.getError();
private <T extends SlackApiRequest, R extends SlackApiTextResponse> R sendRequest(String token, T request, ThrowingBiFunction<MethodsClient, T, R> method) {
MethodsClient client = slack.methods(token);
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) {
error = "unknown error";
}
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("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.scheduling.annotation.Scheduled;
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.queue.discovery.PartitionService;
@ -34,8 +32,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.NOTIFICATION_TABLE
@Slf4j
public class NotificationsCleanUpService extends AbstractCleanUpService {
private final NotificationDao notificationDao;
private final NotificationRequestDao notificationRequestDao;
private final SqlPartitioningRepository partitioningRepository;
@Value("${sql.ttl.notifications.ttl:2592000}")
@ -43,13 +39,8 @@ public class NotificationsCleanUpService extends AbstractCleanUpService {
@Value("${sql.notifications.partition_size:168}")
private int partitionSizeInHours;
public NotificationsCleanUpService(PartitionService partitionService,
NotificationDao notificationDao,
NotificationRequestDao notificationRequestDao,
SqlPartitioningRepository partitioningRepository) {
public NotificationsCleanUpService(PartitionService partitionService, SqlPartitioningRepository partitioningRepository) {
super(partitionService);
this.notificationDao = notificationDao;
this.notificationRequestDao = notificationRequestDao;
this.partitioningRepository = partitioningRepository;
}
@ -59,11 +50,7 @@ public class NotificationsCleanUpService extends AbstractCleanUpService {
long expTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttlInSec);
long partitionDurationMs = TimeUnit.HOURS.toMillis(partitionSizeInHours);
if (isSystemTenantPartitionMine()) {
long actualExpTime = partitioningRepository.getLastPartitionEnd(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 {
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()
.tenantId(tenantId)
.targetId(targetId)
.type("Test")
.templateId(notificationTemplate.getId())
.info(notificationInfo)
.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.setTenantId(tenantId);
notificationTemplate.setName("Notification template for testing");
notificationTemplate.setNotificationType("Just a test");
NotificationTemplateConfig config = new NotificationTemplateConfig();
DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate();
defaultTemplate.setBody(text);
@ -396,6 +396,7 @@ public class NotificationApiTest extends AbstractControllerTest {
EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
emailNotificationTemplate.setSubject("Hello from test");
emailNotificationTemplate.setBody(text);
emailNotificationTemplate.setMethod(deliveryMethod);
config.setTemplates(Map.of(
deliveryMethod, emailNotificationTemplate
));
@ -405,14 +406,6 @@ public class NotificationApiTest extends AbstractControllerTest {
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 {
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;
public enum NotificationSeverity {
NORMAL,
CRITICAL,
URGENT // send pop-up error
public class AlreadySentException extends RuntimeException {
}

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;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName;
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.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
@ -39,8 +38,6 @@ import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY;
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@ -49,11 +46,9 @@ import static com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY;
public class NotificationRequest extends BaseData<NotificationRequestId> implements HasTenantId, HasName {
private TenantId tenantId;
@NotNull(message = "Target is not specified")
@NotNull
private NotificationTargetId targetId;
@NoXss
private String type;
@NotNull
private NotificationTemplateId templateId;
@Valid
@ -77,7 +72,7 @@ public class NotificationRequest extends BaseData<NotificationRequestId> impleme
@JsonIgnore
@Override
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 java.util.Map;
import javax.validation.constraints.Max;
@Data
public class NotificationRequestConfig {
@Max(value = 604800, message = "cannot be longer than 1 week")
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) {
if (error instanceof AlreadySentException) {
return;
}
String errorMessage = error.getMessage();
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
private List<NotificationDeliveryMethod> deliveryMethods;
@NotNull
private NotificationTargetId initialNotificationTargetId;
@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.id.NotificationTemplateId;
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
@EqualsAndHashCode(callSuper = true)
public class NotificationTemplate extends BaseData<NotificationTemplateId> implements HasTenantId, HasName {
private TenantId tenantId;
@NoXss
@NotNull
private String name;
@NoXss
@NotNull
private String notificationType;
@Valid
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_TEMPLATE_ID_PROPERTY = "template_id";
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_ORIGINATOR_TYPE_PROPERTY = "originator_type";
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_TEMPLATE_ID_PROPERTY = "template_id";
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_ESCALATION_CONFIG_PROPERTY = "escalation_config";
public static final String NOTIFICATION_RULE_CONFIGURATION_PROPERTY = "configuration";
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};

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)
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)
private UUID templateId;
@ -107,7 +104,6 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
setCreatedTime(notificationRequest.getCreatedTime());
setTenantId(getUuid(notificationRequest.getTenantId()));
setTargetId(getUuid(notificationRequest.getTargetId()));
setType(notificationRequest.getType());
setTemplateId(getUuid(notificationRequest.getTemplateId()));
setInfo(toJson(notificationRequest.getInfo()));
setDeliveryMethods(StringUtils.join(notificationRequest.getDeliveryMethods(), ','));
@ -129,7 +125,6 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
notificationRequest.setCreatedTime(createdTime);
notificationRequest.setTenantId(createId(tenantId, TenantId::new));
notificationRequest.setTargetId(createId(targetId, NotificationTargetId::new));
notificationRequest.setType(type);
notificationRequest.setTemplateId(createId(templateId, NotificationTemplateId::new));
notificationRequest.setInfo(fromJson(info, NotificationInfo.class));
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.TypeDef;
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.TenantId;
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.NotificationRuleConfig;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
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)
private String deliveryMethods;
@Column(name = ModelConstants.NOTIFICATION_RULE_INITIAL_NOTIFICATION_TARGET_ID_PROPERTY)
private UUID initialNotificationTargetId;
@Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_RULE_ESCALATION_CONFIG_PROPERTY)
private JsonNode escalationConfig;
@Column(name = ModelConstants.NOTIFICATION_RULE_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration;
public NotificationRuleEntity() {}
@ -73,8 +69,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
setName(notificationRule.getName());
setTemplateId(getUuid(notificationRule.getTemplateId()));
setDeliveryMethods(StringUtils.join(notificationRule.getDeliveryMethods(), ','));
setInitialNotificationTargetId(getUuid(notificationRule.getInitialNotificationTargetId()));
setEscalationConfig(toJson(notificationRule.getEscalationConfig()));
setConfiguration(toJson(notificationRule.getConfiguration()));
}
@Override
@ -89,8 +84,7 @@ public class NotificationRuleEntity extends BaseSqlEntity<NotificationRule> {
notificationRule.setDeliveryMethods(Arrays.stream(StringUtils.split(deliveryMethods, ','))
.filter(StringUtils::isNotBlank).map(NotificationDeliveryMethod::valueOf).collect(Collectors.toList()));
}
notificationRule.setInitialNotificationTargetId(createId(initialNotificationTargetId, NotificationTargetId::new));
notificationRule.setEscalationConfig(fromJson(escalationConfig, NotificationEscalationConfig.class));
notificationRule.setConfiguration(fromJson(configuration, NotificationRuleConfig.class));
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)
private String name;
@Column(name = ModelConstants.NOTIFICATION_TEMPLATE_NOTIFICATION_TYPE_PROPERTY, nullable = false)
private String notificationType;
@Type(type = "json")
@Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION, nullable = false)
@Column(name = ModelConstants.NOTIFICATION_TEMPLATE_CONFIGURATION_PROPERTY, nullable = false)
private JsonNode configuration;
public NotificationTemplateEntity() {}
@ -57,6 +60,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
setCreatedTime(notificationTemplate.getCreatedTime());
setTenantId(getUuid(notificationTemplate.getTenantId()));
setName(notificationTemplate.getName());
setNotificationType(notificationTemplate.getNotificationType());
setConfiguration(toJson(notificationTemplate.getConfiguration()));
}
@ -67,6 +71,7 @@ public class NotificationTemplateEntity extends BaseSqlEntity<NotificationTempla
notificationTemplate.setCreatedTime(createdTime);
notificationTemplate.setTenantId(createId(tenantId, TenantId::fromUUID));
notificationTemplate.setName(name);
notificationTemplate.setNotificationType(notificationType);
notificationTemplate.setConfiguration(fromJson(configuration, NotificationTemplateConfig.class));
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
* limitations under the License.
*/
package org.thingsboard.server.service.notification;
package org.thingsboard.server.dao.notification;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
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.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
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.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 java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Component
@Service
@RequiredArgsConstructor
public class NotificationManagerHelper {
public class DefaultNotificationSettingsService implements NotificationSettingsService {
public static final String SETTINGS_KEY = "notifications";
private final NotificationTemplateService templateService;
private final AdminSettingsService adminSettingsService;
public Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> getTemplates(TenantId tenantId, NotificationTemplateId templateId, List<NotificationDeliveryMethod> deliveryMethods) {
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());
}));
}
private static final String SETTINGS_KEY = "notifications";
public String processTemplate(String template, Map<String, String> templateContext) {
return TbNodeUtils.processTemplate(template, templateContext);
@Override
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))
.map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), NotificationSettings.class))
.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 java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
@ -64,6 +65,7 @@ public class DefaultNotificationTargetService implements NotificationTargetServi
@Override
public PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId, PageLink pageLink) {
NotificationTarget notificationTarget = findNotificationTargetById(tenantId, notificationTargetId);
Objects.requireNonNull(notificationTarget, "Notification target [" + notificationTargetId + "] not found");
NotificationTargetConfig configuration = notificationTarget.getConfiguration();
return findRecipientsForNotificationTargetConfig(tenantId, configuration, pageLink);
}
@ -103,7 +105,6 @@ public class DefaultNotificationTargetService implements NotificationTargetServi
@Override
public void deleteNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId) {
notificationTargetDao.removeById(tenantId, notificationTargetId.getId());
// todo: delete related notification requests (?)
}
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;
import com.google.common.base.Strings;
import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
@ -48,8 +47,7 @@ public class JpaNotificationRequestDao extends JpaAbstractDao<NotificationReques
@Override
public PageData<NotificationRequest> findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRequestRepository.findByTenantIdAndSearchText(tenantId.getId(),
Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink)));
return DaoUtil.toPageData(notificationRequestRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override

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

@ -34,10 +34,7 @@ import java.util.UUID;
@Repository
public interface NotificationRequestRepository extends JpaRepository<NotificationRequestEntity, UUID> {
@Query("SELECT r FROM NotificationRequestEntity r WHERE r.tenantId = :tenantId AND " +
"(lower(r.type) LIKE lower(concat('%', :searchText, '%')))")
Page<NotificationRequestEntity> findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId,
@Param("searchText") String searchText, Pageable pageable);
Page<NotificationRequestEntity> findByTenantId(UUID tenantId, Pageable pageable);
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);
Map<Long, SqlPartition> partitions = tablesPartitions.computeIfAbsent(table, t -> new ConcurrentHashMap<>());
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();
try {
if (partitions.containsKey(partitionStartTs)) return;
@ -82,7 +82,7 @@ public class SqlPartitioningRepository {
public void dropPartitionsBefore(String table, long ts, long partitionDurationMs) {
List<Long> partitions = fetchPartitions(table);
for (Long partitionStartTime : partitions) {
long partitionEndTime = partitionStartTime + partitionDurationMs;
long partitionEndTime = getPartitionEndTime(partitionStartTime, partitionDurationMs);
if (partitionEndTime < ts) {
log.info("[{}] Detaching expired partition: [{}-{}]", table, partitionStartTime, partitionEndTime);
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) {
Map<Long, SqlPartition> partitions = tablesPartitions.get(table);
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) {
@ -129,6 +122,10 @@ public class SqlPartitioningRepository {
return false;
}
private static long getPartitionEndTime(long startTime, long partitionDurationMs) {
return startTime + partitionDurationMs;
}
public List<Long> fetchPartitions(String table) {
List<Long> partitions = new ArrayList<>();
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,
tenant_id UUID NOT NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
notification_type VARCHAR(255) 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,
name VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id),
delivery_methods VARCHAR(255),
initial_notification_target_id UUID NULL CONSTRAINT fk_notification_rule_target_id REFERENCES notification_target(id),
escalation_config VARCHAR(500)
delivery_methods VARCHAR(255) NOT NULL,
configuration VARCHAR(2000) NOT NULL
);
CREATE TABLE IF NOT EXISTS notification_request (
@ -812,7 +812,6 @@ CREATE TABLE IF NOT EXISTS notification_request (
created_time BIGINT NOT NULL,
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),
type VARCHAR(255) NOT NULL,
template_id UUID NOT NULL CONSTRAINT fk_notification_request_template_id REFERENCES notification_template(id),
info VARCHAR(1000),
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 org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer;
@ -279,6 +280,8 @@ public interface TbContext {
NotificationManager getNotificationManager();
SlackService getSlackService();
/**
* Creates JS Script Engine
* @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
* limitations under the License.
*/
package org.thingsboard.server.service.slack;
package org.thingsboard.rule.engine.api.slack;
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
* 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;
@ -21,8 +21,12 @@ import java.util.List;
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;
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.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.NotificationRequest;
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 static org.thingsboard.common.util.DonAsynchron.withCallback;
@RuleNode(
type = ComponentType.ACTION,
type = ComponentType.EXTERNAL,
name = "send notification",
configClazz = TbNotificationNodeConfiguration.class,
nodeDescription = "Sends notification to a target",
@ -48,39 +45,31 @@ public class TbNotificationNode implements TbNode {
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbNotificationNodeConfiguration.class);
validateConfig(config);
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
// NotificationRequest notificationRequest = NotificationRequest.builder()
// .tenantId(ctx.getTenantId())
// .targetId(new NotificationTargetId(config.getTargetId()))
// .type(config.getNotificationReason())
// .textTemplate(TbNodeUtils.processPattern(config.getNotificationTextTemplate(), msg))
// .notificationSeverity(config.getNotificationSeverity())
// .originatorType(NotificationOriginatorType.RULE_NODE)
// .originatorEntityId(ctx.getTenantId())
// .build();
// 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));
}
NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(ctx.getTenantId())
.targetId(config.getTargetId())
.templateId(config.getTemplateId())
.deliveryMethods(config.getDeliveryMethods())
.originatorType(NotificationOriginatorType.RULE_NODE)
.originatorEntityId(ctx.getSelfId())
.build();
notificationRequest.setTemplateContext(msg.getMetaData().getData());
private void validateConfig(TbNotificationNodeConfiguration config) throws TbNodeException {
if (config.getTargetId() == null) {
throw new TbNodeException("Notification target is not specified");
}
if (StringUtils.isBlank(config.getNotificationTextTemplate())) {
throw new TbNodeException("Notification text template is missing");
}
DonAsynchron.withCallback(ctx.getDbCallbackExecutor().executeAsync(() -> {
return ctx.getNotificationManager().processNotificationRequest(ctx.getTenantId(), notificationRequest);
}),
r -> {
TbMsgMetaData msgMetaData = msg.getMetaData().copy();
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 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;
@Data
public class TbNotificationNodeConfiguration implements NodeConfiguration<TbNotificationNodeConfiguration> {
private UUID targetId;
private String notificationReason;
private String notificationTextTemplate;
private NotificationSeverity notificationSeverity;
@NotNull
private NotificationTargetId targetId;
@NotNull
private NotificationTemplateId templateId;
@NotEmpty
private List<NotificationDeliveryMethod> deliveryMethods;
private NotificationRequestConfig additionalConfig;
@Override
public TbNotificationNodeConfiguration defaultConfiguration() {
TbNotificationNodeConfiguration configuration = new TbNotificationNodeConfiguration();
configuration.setNotificationReason("General");
configuration.setNotificationSeverity(NotificationSeverity.NORMAL);
return configuration;
TbNotificationNodeConfiguration config = new TbNotificationNodeConfiguration();
config.setTargetId(new NotificationTargetId(UUID.randomUUID()));
config.setTemplateId(new NotificationTemplateId(UUID.randomUUID()));
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