From 91a4de77809c8657665e9ddd4cf7c2eeb2d70805 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 12 Jan 2023 15:01:23 +0200 Subject: [PATCH] Send notifications via Slack independently of the targets --- .../DefaultNotificationCenter.java | 56 ++++++++++------ .../NotificationProcessingContext.java | 12 ++-- .../AbstractNotificationApiTest.java | 24 ++++++- .../notification/NotificationApiTest.java | 67 +++++++++++++++++-- .../NotificationDeliveryMethod.java | 20 +++++- .../notification/NotificationRequest.java | 2 +- .../NotificationRequestStats.java | 10 ++- .../settings/NotificationSettings.java | 2 + .../template/NotificationTemplateConfig.java | 1 + 9 files changed, 156 insertions(+), 38 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index 7eb78edd57..502bded58c 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -64,6 +64,7 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpd import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -130,27 +131,43 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple .build(); ctx.init(); + Set deliveryMethods = ctx.getDeliveryMethods(); List> results = new ArrayList<>(); + for (NotificationTargetId targetId : notificationRequest.getTargets()) { DaoUtil.processBatches(pageLink -> { return notificationTargetService.findRecipientsForNotificationTarget(tenantId, ctx.getCustomerId(), targetId, pageLink); }, 200, recipientsBatch -> { - for (NotificationDeliveryMethod deliveryMethod : ctx.getDeliveryMethods()) { + for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { + if (deliveryMethod.isIndependent()) continue; + List recipients = recipientsBatch.getData(); log.debug("Sending {} notifications for request {} to recipients batch ({})", deliveryMethod, savedNotificationRequest.getId(), recipients.size()); NotificationChannel notificationChannel = channels.get(deliveryMethod); for (User recipient : recipients) { - ListenableFuture resultFuture = processForRecipient(notificationChannel, recipient, ctx); + ListenableFuture resultFuture = process(notificationChannel, recipient, ctx); DonAsynchron.withCallback(resultFuture, result -> { ctx.getStats().reportSent(deliveryMethod, recipient); }, error -> { - ctx.getStats().reportError(deliveryMethod, recipient, error); + ctx.getStats().reportError(deliveryMethod, error, recipient); }); results.add(resultFuture); } } }); } + for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { + if (deliveryMethod.isIndependent()) { + NotificationChannel notificationChannel = channels.get(deliveryMethod); + ListenableFuture resultFuture = process(notificationChannel, null, ctx); + DonAsynchron.withCallback(resultFuture, result -> { + ctx.getStats().reportSent(deliveryMethod, null); + }, error -> { + ctx.getStats().reportError(deliveryMethod, error, null); + }); + results.add(resultFuture); + } + } Futures.whenAllComplete(results).run(() -> { NotificationRequestStats stats = ctx.getStats(); @@ -177,33 +194,21 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple return savedNotificationRequest; } - private ListenableFuture processForRecipient(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) { + private ListenableFuture process(NotificationChannel notificationChannel, User recipient, NotificationProcessingContext ctx) { NotificationDeliveryMethod deliveryMethod = notificationChannel.getDeliveryMethod(); - if (ctx.getStats().contains(deliveryMethod, recipient.getId())) { + if (recipient != null && ctx.getStats().contains(deliveryMethod, recipient.getId())) { return Futures.immediateFailedFuture(new AlreadySentException()); } DeliveryMethodNotificationTemplate processedTemplate; try { - processedTemplate = ctx.getProcessedTemplate(deliveryMethod, recipient); + Map templateContext = recipient != null ? ctx.createTemplateContext(recipient) : Collections.emptyMap(); + processedTemplate = ctx.getProcessedTemplate(deliveryMethod, templateContext); } catch (Exception e) { return Futures.immediateFailedFuture(e); } return notificationChannel.sendNotification(recipient, processedTemplate, ctx); } - private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) { - TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() - .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) - .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) - .setRequestIdMSB(notificationRequestId.getId().getMostSignificantBits()) - .setRequestIdLSB(notificationRequestId.getId().getLeastSignificantBits()) - .setTs(System.currentTimeMillis()); - TransportProtos.ToCoreMsg toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setNotificationSchedulerServiceMsg(msg) - .build(); - clusterService.pushMsgToCore(tenantId, notificationRequestId, toCoreMsg, null); - } - @Override public ListenableFuture sendNotification(User recipient, PushDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { NotificationRequest request = ctx.getRequest(); @@ -302,6 +307,19 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple return notificationRequest; } + private void forwardToNotificationSchedulerService(TenantId tenantId, NotificationRequestId notificationRequestId) { + TransportProtos.NotificationSchedulerServiceMsg.Builder msg = TransportProtos.NotificationSchedulerServiceMsg.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setRequestIdMSB(notificationRequestId.getId().getMostSignificantBits()) + .setRequestIdLSB(notificationRequestId.getId().getLeastSignificantBits()) + .setTs(System.currentTimeMillis()); + TransportProtos.ToCoreMsg toCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setNotificationSchedulerServiceMsg(msg) + .build(); + clusterService.pushMsgToCore(tenantId, notificationRequestId, toCoreMsg, null); + } + private ListenableFuture onNotificationUpdate(TenantId tenantId, UserId recipientId, NotificationUpdate update) { log.trace("Submitting notification update for recipient {}: {}", recipientId, update); return Futures.submit(() -> { diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java index 1f054b8938..39188679e3 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java @@ -90,8 +90,11 @@ public class NotificationProcessingContext { return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod); } - protected T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, User recipient) { - Map templateContext = createTemplateContext(recipient); + protected T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map templateContext) { + if (request.getInfo() != null) { + templateContext = new HashMap<>(templateContext); + templateContext.putAll(request.getInfo().getTemplateData()); + } T template = (T) templates.get(deliveryMethod).copy(); template.setBody(processTemplate(template.getBody(), templateContext)); @@ -106,14 +109,11 @@ public class NotificationProcessingContext { return TbNodeUtils.processTemplate(template, context); } - private Map createTemplateContext(User recipient) { + public Map createTemplateContext(User recipient) { Map templateContext = new HashMap<>(); templateContext.put("email", recipient.getEmail()); templateContext.put("firstName", Strings.nullToEmpty(recipient.getFirstName())); templateContext.put("lastName", Strings.nullToEmpty(recipient.getLastName())); - if (request.getInfo() != null) { - templateContext.putAll(request.getInfo().getTemplateData()); - } return templateContext; } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index 341eca8ef5..348fdf0e11 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -25,13 +25,16 @@ import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; @@ -52,6 +55,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class AbstractNotificationApiTest extends AbstractControllerTest { @@ -94,20 +98,28 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.PUSH}; } NotificationTemplate notificationTemplate = createNotificationTemplate(DEFAULT_NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_SUBJECT, text, deliveryMethods); + return submitNotificationRequest(targets, notificationTemplate.getId(), delayInSec); + } + + protected NotificationRequest submitNotificationRequest(List targets, NotificationTemplateId notificationTemplateId, int delayInSec) { NotificationRequestConfig config = new NotificationRequestConfig(); config.setSendingDelayInSec(delayInSec); UserOriginatedNotificationInfo notificationInfo = new UserOriginatedNotificationInfo(); - notificationInfo.setDescription("The text: " + text); + notificationInfo.setDescription("My description"); NotificationRequest notificationRequest = NotificationRequest.builder() .tenantId(tenantId) .targets(targets) - .templateId(notificationTemplate.getId()) + .templateId(notificationTemplateId) .info(notificationInfo) .additionalConfig(config) .build(); return doPost("/api/notification/request", notificationRequest, NotificationRequest.class); } + protected NotificationRequestStats getStats(NotificationRequestId notificationRequestId) throws Exception { + return findNotificationRequest(notificationRequestId).getStats(); + } + protected NotificationTemplate createNotificationTemplate(NotificationType notificationType, String subject, String text, NotificationDeliveryMethod... deliveryMethods) { NotificationTemplate notificationTemplate = new NotificationTemplate(); @@ -143,9 +155,17 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest config.getDeliveryMethodsTemplates().put(deliveryMethod, deliveryMethodNotificationTemplate); } notificationTemplate.setConfiguration(config); + return saveNotificationTemplate(notificationTemplate); + } + + protected NotificationTemplate saveNotificationTemplate(NotificationTemplate notificationTemplate) { return doPost("/api/notification/template", notificationTemplate, NotificationTemplate.class); } + protected void saveNotificationSettings(NotificationSettings notificationSettings) throws Exception { + doPost("/api/notification/settings", notificationSettings).andExpect(status().isOk()); + } + protected Pair createUserAndConnectWsClient(Authority authority) throws Exception { User user = new User(); user.setTenantId(tenantId); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 79656a9c14..4282091ecd 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -30,9 +30,16 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStatus; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.info.UserOriginatedNotificationInfo; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.targets.AllUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +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.SlackConversation; +import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -43,6 +50,7 @@ import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCou import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -55,6 +63,7 @@ import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.startsWith; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -312,8 +321,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest { }); await().atMost(2, TimeUnit.SECONDS) - .until(() -> findNotificationRequest(notificationRequest.getId()).getStats() != null); - NotificationRequestStats stats = findNotificationRequest(notificationRequest.getId()).getStats(); + .until(() -> getStats(notificationRequest.getId()) != null); + NotificationRequestStats stats = getStats(notificationRequest.getId()); assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(usersCount); assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(usersCount); @@ -341,8 +350,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest { wsClient.waitForUpdate(); await().atMost(2, TimeUnit.SECONDS) - .until(() -> findNotificationRequest(notificationRequest.getId()).getStats() != null); - NotificationRequestStats stats = findNotificationRequest(notificationRequest.getId()).getStats(); + .until(() -> getStats(notificationRequest.getId()) != null); + NotificationRequestStats stats = getStats(notificationRequest.getId()); assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(1); assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(1); @@ -360,7 +369,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest { user.setAuthority(Authority.TENANT_ADMIN); user.setEmail("test-user-" + i + "@thingsboard.org"); user = doPost("/api/user", user, User.class); - System.err.println(i); users.add(user); } @@ -389,10 +397,57 @@ public class NotificationApiTest extends AbstractNotificationApiTest { assertThat(sentNotifications.getData()).extracting(Notification::getRecipientId) .containsAll(users.stream().map(User::getId).collect(Collectors.toSet())); - NotificationRequestStats stats = findNotificationRequest(notificationRequest.getId()).getStats(); + NotificationRequestStats stats = getStats(notificationRequest.getId()); assertThat(stats.getSent().values().stream().mapToInt(AtomicInteger::get).sum()).isGreaterThanOrEqualTo(usersCount); } + @Test + public void testSlackNotifications() throws Exception { + NotificationSettings settings = new NotificationSettings(); + SlackNotificationDeliveryMethodConfig slackConfig = new SlackNotificationDeliveryMethodConfig(); + slackConfig.setMethod(NotificationDeliveryMethod.SLACK); + slackConfig.setEnabled(true); + String slackToken = "xoxb-123123123"; + slackConfig.setBotToken(slackToken); + settings.setDeliveryMethodsConfigs(Map.of( + NotificationDeliveryMethod.SLACK, slackConfig + )); + saveNotificationSettings(settings); + + NotificationTemplate notificationTemplate = new NotificationTemplate(); + notificationTemplate.setName("Slack notification template"); + notificationTemplate.setNotificationType(NotificationType.GENERAL); + NotificationTemplateConfig config = new NotificationTemplateConfig(); + config.setDefaultTextTemplate("To Slack :)"); + + SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate(); + slackNotificationTemplate.setEnabled(true); + slackNotificationTemplate.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL); + String conversationId = "U154475415"; + slackNotificationTemplate.setConversationId(conversationId); + + config.setDeliveryMethodsTemplates(Map.of( + NotificationDeliveryMethod.SLACK, slackNotificationTemplate + )); + notificationTemplate.setConfiguration(config); + notificationTemplate = saveNotificationTemplate(notificationTemplate); + + NotificationRequest successfulNotificationRequest = submitNotificationRequest(Collections.emptyList(), notificationTemplate.getId(), 0); + await().atMost(2, TimeUnit.SECONDS) + .until(() -> getStats(successfulNotificationRequest.getId()) != null); + verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq(config.getDefaultTextTemplate())); + NotificationRequestStats stats = getStats(successfulNotificationRequest.getId()); + assertThat(stats.getSent().get(NotificationDeliveryMethod.SLACK)).hasValue(1); + + String errorMessage = "Error!!!"; + doThrow(new RuntimeException(errorMessage)).when(slackService).sendMessage(any(), any(), any(), any()); + NotificationRequest failedNotificationRequest = submitNotificationRequest(Collections.emptyList(), notificationTemplate.getId(), 0); + await().atMost(2, TimeUnit.SECONDS) + .until(() -> getStats(failedNotificationRequest.getId()) != null); + stats = getStats(failedNotificationRequest.getId()); + assertThat(stats.getErrors().get(NotificationDeliveryMethod.SLACK).values()).containsExactly(errorMessage); + } + private void checkFullNotificationsUpdate(UnreadNotificationsUpdate notificationsUpdate, String... expectedNotifications) { assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getText).containsOnly(expectedNotifications); assertThat(notificationsUpdate.getNotifications()).extracting(Notification::getType).containsOnly(DEFAULT_NOTIFICATION_TYPE); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java index b179535238..f34c375634 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java @@ -15,6 +15,24 @@ */ package org.thingsboard.server.common.data.notification; +import lombok.Getter; + public enum NotificationDeliveryMethod { - PUSH, EMAIL, SMS, SLACK + + PUSH, + EMAIL, + SMS, + SLACK(true); + + @Getter + private final boolean independent; // TODO: think of a better name + + NotificationDeliveryMethod() { + this(false); + } + + NotificationDeliveryMethod(boolean independent) { + this.independent = independent; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 67da203907..e8e9e967e2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -46,7 +46,7 @@ import java.util.List; public class NotificationRequest extends BaseData implements HasTenantId, HasName { private TenantId tenantId; - @NotEmpty + @NotNull private List targets; @NotNull diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java index 34c9d70ed8..925c401b89 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.UserId; import java.util.Collections; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -52,15 +53,18 @@ public class NotificationRequestStats { public void reportSent(NotificationDeliveryMethod deliveryMethod, User recipient) { sent.computeIfAbsent(deliveryMethod, k -> new AtomicInteger()).incrementAndGet(); - processedRecipients.computeIfAbsent(deliveryMethod, k -> ConcurrentHashMap.newKeySet()).add(recipient.getId()); + if (recipient != null) { + processedRecipients.computeIfAbsent(deliveryMethod, k -> ConcurrentHashMap.newKeySet()).add(recipient.getId()); + } } - public void reportError(NotificationDeliveryMethod deliveryMethod, User recipient, Throwable error) { + public void reportError(NotificationDeliveryMethod deliveryMethod, Throwable error, User recipient) { if (error instanceof AlreadySentException) { return; } String errorMessage = error.getMessage(); - errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(recipient.getEmail(), errorMessage); + String key = Optional.ofNullable(recipient).map(User::getEmail).orElse(""); + errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(key, errorMessage); } public boolean contains(NotificationDeliveryMethod deliveryMethod) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java index 4401473efb..ae778f4e68 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationSettings.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.notification.settings; import lombok.Data; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import javax.validation.Valid; import javax.validation.constraints.NotNull; import java.util.Map; @@ -25,6 +26,7 @@ import java.util.Map; public class NotificationSettings { @NotNull + @Valid // location on the screen, shown notifications count, timings of displaying private Map deliveryMethodsConfigs; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java index 3fc07686d6..d4dcd7f4d2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplateConfig.java @@ -37,6 +37,7 @@ public class NotificationTemplateConfig { @JsonIgnore @AssertTrue(message = "defaultTextTemplate and notificationSubject must be specified if one absent for delivery method") public boolean isValid() { + if (deliveryMethodsTemplates == null) return true; for (DeliveryMethodNotificationTemplate template : deliveryMethodsTemplates.values()) { if (StringUtils.isEmpty(template.getBody()) && StringUtils.isEmpty(defaultTextTemplate)) { return false;