Slack notification channel; refactoring

This commit is contained in:
ViacheslavKlimov 2022-12-19 19:56:20 +02:00
parent 4b39d2a221
commit 738b9dc494
23 changed files with 631 additions and 126 deletions

View File

@ -356,6 +356,10 @@
<groupId>org.jboss.aerogear</groupId> <groupId>org.jboss.aerogear</groupId>
<artifactId>aerogear-otp-java</artifactId> <artifactId>aerogear-otp-java</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.slack.api</groupId>
<artifactId>slack-api-client</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -79,16 +79,3 @@ CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notificat
ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID; ALTER TABLE alarm ADD COLUMN IF NOT EXISTS notification_rule_id UUID;
ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255); ALTER TABLE tb_user ADD COLUMN IF NOT EXISTS phone VARCHAR(255);
CREATE OR REPLACE FUNCTION on_notification_deleted() RETURNS TRIGGER as $notification_deleted$
BEGIN
RAISE NOTICE 'ABAAAAAAAAAAAAAAAAAAA';
INSERT INTO id_and_time values ('13814000-1dd2-11b2-8080-808080808080', 0);
RETURN NULL;
END;
$notification_deleted$ LANGUAGE plpgsql;
CREATE TRIGGER notification_deleted_trigger
AFTER DELETE ON id_and_time
REFERENCING OLD TABLE AS deleted
FOR EACH STATEMENT EXECUTE FUNCTION on_notification_deleted();

View File

@ -35,17 +35,24 @@ 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.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.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.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.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.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.UUID; import java.util.UUID;
@RestController @RestController
@ -58,6 +65,8 @@ 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 SlackService slackService;
@GetMapping("/notifications") @GetMapping("/notifications")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@ -133,4 +142,33 @@ public class NotificationController extends BaseController {
doDeleteAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::deleteNotificationRequest); doDeleteAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationManager::deleteNotificationRequest);
} }
@PostMapping("/notification/settings")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationSettings saveNotificationSettings(@RequestBody NotificationSettings notificationSettings,
@AuthenticationPrincipal SecurityUser user) {
notificationManagerHelper.saveNotificationSettings(user.getTenantId(), notificationSettings);
return notificationSettings;
}
@GetMapping("/notification/settings")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationSettings getNotificationSettings(@AuthenticationPrincipal SecurityUser user) {
return notificationManagerHelper.getNotificationSettings(user.getTenantId());
}
@GetMapping("/notification/slack/conversations")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public List<SlackConversation> listSlackConversations(@RequestParam SlackConversation.Type type,
@AuthenticationPrincipal SecurityUser user) throws Exception {
NotificationSettings settings = getNotificationSettings(user);
SlackNotificationDeliveryMethodConfig slackConfig = (SlackNotificationDeliveryMethodConfig)
settings.getDeliveryMethodsConfigs().get(NotificationDeliveryMethod.SLACK);
if (slackConfig == null) {
throw new IllegalArgumentException("Slack is not configured");
}
return slackService.listConversations(user.getTenantId(), slackConfig.getBotToken(), type);
}
} }

View File

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.notification; package org.thingsboard.server.service.notification;
import com.google.common.base.Strings;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -33,11 +32,10 @@ import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.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.template.NotificationText; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
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;
@ -57,7 +55,6 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpd
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -74,7 +71,7 @@ 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 NotificationTemplateUtil notificationTemplateUtil; private final NotificationManagerHelper notificationManagerHelper;
private final DbCallbackExecutorService dbCallbackExecutorService; private final DbCallbackExecutorService dbCallbackExecutorService;
private final NotificationsTopicService notificationsTopicService; private final NotificationsTopicService notificationsTopicService;
private final TbQueueProducerProvider producerProvider; private final TbQueueProducerProvider producerProvider;
@ -85,6 +82,13 @@ 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);
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");
}
});
if (notificationRequest.getAdditionalConfig() != null) { if (notificationRequest.getAdditionalConfig() != null) {
NotificationRequestConfig config = notificationRequest.getAdditionalConfig(); NotificationRequestConfig config = notificationRequest.getAdditionalConfig();
if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) { if (config.getSendingDelayInSec() > 0 && notificationRequest.getId() == null) {
@ -98,9 +102,13 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
notificationRequest.setStatus(NotificationRequestStatus.PROCESSED); notificationRequest.setStatus(NotificationRequestStatus.PROCESSED);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
NotificationRequestStats stats = new NotificationRequestStats(); NotificationProcessingContext ctx = NotificationProcessingContext.builder()
Map<NotificationDeliveryMethod, NotificationTextTemplate> textTemplates = notificationTemplateUtil.getTemplates(tenantId, notificationRequest.getTemplateId(), savedNotificationRequest.getDeliveryMethods()); .tenantId(tenantId)
savedNotificationRequest.setTemplateContext(notificationRequest.getTemplateContext()); .settings(settings)
.request(savedNotificationRequest)
.additionalTemplateContext(notificationRequest.getTemplateContext())
.build();
ctx.init(notificationManagerHelper);
DaoUtil.processBatches(pageLink -> { DaoUtil.processBatches(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink); return notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId(), pageLink);
@ -111,20 +119,19 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId()); log.debug("Sending {} notifications for request {} to recipients batch", deliveryMethod, savedNotificationRequest.getId());
List<User> recipients = recipientsBatch.getData(); List<User> recipients = recipientsBatch.getData();
NotificationTextTemplate textTemplate = textTemplates.get(deliveryMethod);
for (User recipient : recipients) { for (User recipient : recipients) {
ListenableFuture<Void> resultFuture = processForRecipient(recipient, savedNotificationRequest, notificationChannel, textTemplate, stats); ListenableFuture<Void> resultFuture = processForRecipient(notificationChannel, recipient, ctx);
DonAsynchron.withCallback(resultFuture, result -> { DonAsynchron.withCallback(resultFuture, result -> {
stats.reportSent(deliveryMethod); ctx.getStats().reportSent(deliveryMethod);
}, error -> { }, error -> {
stats.reportError(deliveryMethod, recipient, error); ctx.getStats().reportError(deliveryMethod, recipient, error);
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
results.add(resultFuture); results.add(resultFuture);
} }
} }
Futures.whenAllComplete(results).run(() -> { Futures.whenAllComplete(results).run(() -> {
try { try {
notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), stats); notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), ctx.getStats());
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to update stats for notification request {}", savedNotificationRequest.getId(), e); log.error("Failed to update stats for notification request {}", savedNotificationRequest.getId(), e);
} }
@ -134,22 +141,15 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
return savedNotificationRequest; return savedNotificationRequest;
} }
private ListenableFuture<Void> processForRecipient(User recipient, NotificationRequest notificationRequest, NotificationChannel notificationChannel, private ListenableFuture<Void> processForRecipient(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) {
NotificationTextTemplate textTemplate, NotificationRequestStats stats) { String text;
NotificationText text;
try { try {
Map<String, String> templateContext = new HashMap<>(); DeliveryMethodNotificationTemplate template = ctx.getTemplate(notificationChannel.getDeliveryMethod());
templateContext.put("email", recipient.getEmail()); text = notificationManagerHelper.processTemplate(template.getBody(), ctx.createTemplateContext(recipient));
templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName()));
templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName()));
if (notificationRequest.getTemplateContext() != null) {
templateContext.putAll(notificationRequest.getTemplateContext());
}
text = notificationTemplateUtil.processTemplate(textTemplate, templateContext);
} catch (Exception e) { } catch (Exception e) {
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);
} }
return notificationChannel.sendNotification(recipient, notificationRequest, text); return notificationChannel.sendNotification(recipient, text, ctx);
} }
private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) { private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId, boolean deleted) {
@ -167,13 +167,14 @@ public class DefaultNotificationManager extends AbstractSubscriptionService impl
} }
@Override @Override
public ListenableFuture<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text) { public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
NotificationRequest request = ctx.getRequest();
log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), request.getId()); log.trace("Creating notification for recipient {} (notification request id: {})", recipient.getId(), request.getId());
Notification notification = Notification.builder() Notification notification = Notification.builder()
.requestId(request.getId()) .requestId(request.getId())
.recipientId(recipient.getId()) .recipientId(recipient.getId())
.type(request.getType()) .type(request.getType())
.text(text.getBody()) .text(text)
.info(request.getInfo()) .info(request.getInfo())
.originatorType(request.getOriginatorType()) .originatorType(request.getOriginatorType())
.status(NotificationStatus.SENT) .status(NotificationStatus.SENT)

View File

@ -0,0 +1,90 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
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
@RequiredArgsConstructor
public class NotificationManagerHelper {
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());
}));
}
public String processTemplate(String template, Map<String, String> templateContext) {
return TbNodeUtils.processTemplate(template, templateContext);
}
public NotificationSettings getNotificationSettings(TenantId tenantId) {
return Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY))
.map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), NotificationSettings.class))
.orElseGet(() -> {
NotificationSettings settings = new NotificationSettings();
NotificationDeliveryMethodConfig defaultConfig = new NotificationDeliveryMethodConfig();
defaultConfig.setEnabled(true);
settings.setDeliveryMethodsConfigs(Map.of(
NotificationDeliveryMethod.WEBSOCKET, defaultConfig,
NotificationDeliveryMethod.EMAIL, defaultConfig,
NotificationDeliveryMethod.SMS, defaultConfig
));
return settings;
});
}
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);
}
}

View File

@ -0,0 +1,79 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification;
import com.google.common.base.Strings;
import lombok.Builder;
import lombok.Getter;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
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 java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
public class NotificationProcessingContext {
@Getter
private final TenantId tenantId;
private final NotificationSettings settings;
@Getter
private final NotificationRequest request;
private final Map<String, String> additionalTemplateContext;
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates;
@Getter
private NotificationRequestStats stats;
@Builder
public NotificationProcessingContext(TenantId tenantId, NotificationSettings settings, NotificationRequest request, Map<String, String> additionalTemplateContext) {
this.tenantId = tenantId;
this.settings = settings;
this.request = request;
this.additionalTemplateContext = additionalTemplateContext;
}
public void init(NotificationManagerHelper notificationManagerHelper) {
templates = notificationManagerHelper.getTemplates(tenantId, request.getTemplateId(), request.getDeliveryMethods());
stats = new NotificationRequestStats();
}
public <T extends DeliveryMethodNotificationTemplate> T getTemplate(NotificationDeliveryMethod deliveryMethod) {
return (T) templates.get(deliveryMethod);
}
public <C extends NotificationDeliveryMethodConfig> C getDeliveryMethodConfig(NotificationDeliveryMethod deliveryMethod) {
return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod);
}
public Map<String, String> createTemplateContext(User recipient) {
Map<String, String> templateContext = new HashMap<>();
templateContext.put("email", recipient.getEmail());
templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName()));
templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName()));
if (additionalTemplateContext != null) {
templateContext.putAll(additionalTemplateContext);
}
return templateContext;
}
}

View File

@ -1,56 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
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.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.NotificationText;
import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
public class NotificationTemplateUtil {
private final NotificationTemplateService templateService;
public Map<NotificationDeliveryMethod, NotificationTextTemplate> 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.getTextTemplates())
.map(templates -> templates.get(deliveryMethod))
.orElse(config.getDefaultTextTemplate());
}));
}
public NotificationText processTemplate(NotificationTextTemplate template, Map<String, String> templateContext) {
return new NotificationText(TbNodeUtils.processTemplate(template.getBody(), templateContext), template.getSubject());
}
}

View File

@ -21,9 +21,9 @@ import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationText;
import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@ -33,9 +33,10 @@ public class EmailNotificationChannel implements NotificationChannel {
private final MailExecutorService executor; private final MailExecutorService executor;
@Override @Override
public ListenableFuture<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text) { public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
EmailDeliveryMethodNotificationTemplate template = ctx.getTemplate(NotificationDeliveryMethod.EMAIL);
return executor.submit(() -> { return executor.submit(() -> {
mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text.getSubject(), text.getBody()); mailService.sendEmail(recipient.getTenantId(), recipient.getEmail(), text, template.getSubject());
return null; return null;
}); });
} }

View File

@ -18,12 +18,11 @@ package org.thingsboard.server.service.notification.channels;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.template.NotificationText;
public interface NotificationChannel { public interface NotificationChannel {
ListenableFuture<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text); ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx);
NotificationDeliveryMethod getDeliveryMethod(); NotificationDeliveryMethod getDeliveryMethod();

View File

@ -0,0 +1,81 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.notification.channels;
import com.google.common.util.concurrent.Futures;
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.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Component
@RequiredArgsConstructor
public class SlackNotificationChannel implements NotificationChannel {
private final SlackService slackService;
private ExecutorService executor;
@PostConstruct
private void init() {
executor = Executors.newSingleThreadExecutor();
}
@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 (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
}
} else {
String username = StringUtils.join(new String[]{recipient.getFirstName(), recipient.getLastName()}, ' ');
if (StringUtils.isNotEmpty(username)) {
conversationId = username;
} 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
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.SLACK;
}
}

View File

@ -23,8 +23,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.service.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.template.NotificationText;
import org.thingsboard.server.service.sms.SmsExecutorService; import org.thingsboard.server.service.sms.SmsExecutorService;
@Component @Component
@ -35,11 +34,11 @@ public class SmsNotificationChannel implements NotificationChannel {
private final SmsExecutorService executor; private final SmsExecutorService executor;
@Override @Override
public ListenableFuture<Void> sendNotification(User recipient, NotificationRequest request, NotificationText text) { public ListenableFuture<Void> sendNotification(User recipient, String text, NotificationProcessingContext ctx) {
String phone = recipient.getPhone(); String phone = recipient.getPhone();
if (StringUtils.isBlank(phone)) return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number")); if (StringUtils.isBlank(phone)) return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number"));
return executor.submit(() -> { return executor.submit(() -> {
smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text.getBody()); smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, text);
return null; return null;
}); });
} }

View File

@ -0,0 +1,101 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.slack;
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
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 org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class DefaultSlackService implements SlackService {
private final Slack slack = Slack.getInstance();
@Override
public void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception {
ChatPostMessageRequest request = ChatPostMessageRequest.builder()
.channel(conversationId)
.text(message)
.build();
ChatPostMessageResponse response = slack.methods(token).chatPostMessage(request);
check(response);
}
@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());
} 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());
}
}
private void check(SlackApiTextResponse slackResponse) {
if (!slackResponse.isOk()) {
String error = slackResponse.getError();
if (error == null) {
error = "unknown error";
}
if (error.contains("missing_scope")) {
String neededScope = slackResponse.getNeeded();
throw new RuntimeException("Bot token scope '" + neededScope + "' is needed");
}
throw new RuntimeException("Failed to send message via Slack: " + error);
}
}
}

View File

@ -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.service.slack;
import lombok.Data;
@Data
public class SlackConversation {
private String id;
private String name;
public enum Type {
USER,
PUBLIC_CHANNEL,
PRIVATE_CHANNEL
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.slack;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.List;
public interface SlackService {
void sendMessage(TenantId tenantId, String token, String conversationId, String message) throws Exception;
List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversation.Type conversationType) throws Exception;
}

View File

@ -34,12 +34,15 @@ import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig; import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.NotificationTextTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
@ -363,7 +366,7 @@ public class NotificationApiTest extends AbstractControllerTest {
} }
private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) { private NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) {
NotificationTemplate notificationTemplate = createNotificationTemplate(text); NotificationTemplate notificationTemplate = createNotificationTemplate(text, deliveryMethods);
NotificationRequestConfig config = new NotificationRequestConfig(); NotificationRequestConfig config = new NotificationRequestConfig();
config.setSendingDelayInSec(delayInSec); config.setSendingDelayInSec(delayInSec);
NotificationInfo notificationInfo = new NotificationInfo(); NotificationInfo notificationInfo = new NotificationInfo();
@ -380,18 +383,36 @@ public class NotificationApiTest extends AbstractControllerTest {
return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class);
} }
private NotificationTemplate createNotificationTemplate(String text) { private NotificationTemplate createNotificationTemplate(String text, NotificationDeliveryMethod... deliveryMethods) {
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");
NotificationTemplateConfig config = new NotificationTemplateConfig(); NotificationTemplateConfig config = new NotificationTemplateConfig();
NotificationTextTemplate textTemplate = new NotificationTextTemplate(); DeliveryMethodNotificationTemplate defaultTemplate = new DeliveryMethodNotificationTemplate();
textTemplate.setBody(text); defaultTemplate.setBody(text);
config.setDefaultTextTemplate(textTemplate); config.setDefaultTemplate(defaultTemplate);
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
if (deliveryMethod == NotificationDeliveryMethod.EMAIL) {
EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
emailNotificationTemplate.setSubject("Hello from test");
emailNotificationTemplate.setBody(text);
config.setTemplates(Map.of(
deliveryMethod, emailNotificationTemplate
));
}
}
notificationTemplate.setConfiguration(config); notificationTemplate.setConfiguration(config);
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);
} }

View File

@ -51,4 +51,8 @@ public class NotificationRequestStats {
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage); errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage);
} }
public boolean contains(NotificationDeliveryMethod deliveryMethod) {
return sent.containsKey(deliveryMethod) || errors.containsKey(deliveryMethod);
}
} }

View File

@ -17,22 +17,21 @@ package org.thingsboard.server.common.data.notification.settings;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", visible = true, include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = NotificationDeliveryMethodConfig.class) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", include = JsonTypeInfo.As.EXISTING_PROPERTY,
//@JsonSubTypes({ visible = true, defaultImpl = NotificationDeliveryMethodConfig.class)
//// @JsonSubTypes.Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class), @JsonSubTypes({
//}) @Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class),
})
@Data @Data
public class NotificationDeliveryMethodConfig { public class NotificationDeliveryMethodConfig {
private boolean enabled; private boolean enabled;
private NotificationDeliveryMethod method;
public NotificationDeliveryMethod getMethod() {
return null;
}
} }

View File

@ -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.common.data.notification.settings;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class SlackNotificationDeliveryMethodConfig extends NotificationDeliveryMethodConfig {
private String botToken;
}

View File

@ -0,0 +1,36 @@
/**
* 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.template;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method", visible = true, include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = DeliveryMethodNotificationTemplate.class)
@JsonSubTypes({
@JsonSubTypes.Type(name = "EMAIL", value = EmailDeliveryMethodNotificationTemplate.class),
@JsonSubTypes.Type(name = "SLACK", value = SlackDeliveryMethodNotificationTemplate.class)
})
@Data
public class DeliveryMethodNotificationTemplate {
private String body;
private NotificationDeliveryMethod method;
}

View File

@ -16,11 +16,12 @@
package org.thingsboard.server.common.data.notification.template; package org.thingsboard.server.common.data.notification.template;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
@Data @Data
public class NotificationTextTemplate { @EqualsAndHashCode(callSuper = true)
public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate {
private String body;
private String subject; private String subject;
} }

View File

@ -23,7 +23,7 @@ import java.util.Map;
@Data @Data
public class NotificationTemplateConfig { public class NotificationTemplateConfig {
private NotificationTextTemplate defaultTextTemplate; private DeliveryMethodNotificationTemplate defaultTemplate;
private Map<NotificationDeliveryMethod, NotificationTextTemplate> textTemplates; private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates;
} }

View File

@ -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.common.data.notification.template;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate {
private String conversationId; // not required, set from user's name if not set
}

View File

@ -144,6 +144,7 @@
<jgit.version>6.1.0.202203080745-r</jgit.version> <jgit.version>6.1.0.202203080745-r</jgit.version>
<exp4j.version>0.4.8</exp4j.version> <exp4j.version>0.4.8</exp4j.version>
<aerogear-otp.version>1.0.0</aerogear-otp.version> <aerogear-otp.version>1.0.0</aerogear-otp.version>
<slack-api.version>1.12.1</slack-api.version>
</properties> </properties>
<modules> <modules>
@ -1945,6 +1946,11 @@
<artifactId>aerogear-otp-java</artifactId> <artifactId>aerogear-otp-java</artifactId>
<version>${aerogear-otp.version}</version> <version>${aerogear-otp.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.slack.api</groupId>
<artifactId>slack-api-client</artifactId>
<version>${slack-api.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jgit</groupId> <groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId> <artifactId>org.eclipse.jgit</artifactId>