Browse Source

Merge remote-tracking branch 'klimov/feature/notification-system' into feature/notification-system

pull/7980/head
Vladyslav_Prykhodko 4 years ago
parent
commit
0e3d98afbc
  1. 45
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  2. 9
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  3. 4
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationRuleProcessingService.java
  4. 2
      application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
  5. 5
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  6. 131
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  7. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java
  8. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java
  9. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java
  10. 31
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestPreview.java
  11. 1
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStatus.java
  12. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java
  13. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/PushDeliveryMethodNotificationTemplate.java
  14. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java
  15. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java
  16. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java
  17. 4
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java
  18. 11
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  19. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java
  20. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java
  21. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java
  22. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNodeConfiguration.java

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

@ -33,22 +33,32 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.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.dao.notification.NotificationSettingsService; 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.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
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 javax.validation.Valid; import javax.validation.Valid;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@ -59,6 +69,8 @@ public class NotificationController extends BaseController {
private final NotificationService notificationService; private final NotificationService notificationService;
private final NotificationRequestService notificationRequestService; private final NotificationRequestService notificationRequestService;
private final NotificationTemplateService notificationTemplateService;
private final NotificationTargetService notificationTargetService;
private final NotificationCenter notificationCenter; private final NotificationCenter notificationCenter;
private final NotificationSettingsService notificationSettingsService; private final NotificationSettingsService notificationSettingsService;
@ -112,6 +124,39 @@ public class NotificationController extends BaseController {
return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationCenter::processNotificationRequest); return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationCenter::processNotificationRequest);
} }
@PostMapping("/notification/request/preview")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRequestPreview getNotificationRequestPreview(@RequestBody @Valid NotificationRequest notificationRequest,
@AuthenticationPrincipal SecurityUser user) {
NotificationRequestPreview preview = new NotificationRequestPreview();
notificationRequest.setOriginatorEntityId(user.getId());
NotificationTemplate notificationTemplate = notificationTemplateService.findNotificationTemplateById(user.getTenantId(), notificationRequest.getTemplateId());
NotificationProcessingContext mockProcessingCtx = NotificationProcessingContext.builder()
.tenantId(user.getTenantId())
.request(notificationRequest)
.settings(null)
.template(notificationTemplate)
.build();
mockProcessingCtx.init();
Map<String, String> templateContext = mockProcessingCtx.createTemplateContext(user);
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = mockProcessingCtx.getDeliveryMethods().stream()
.collect(Collectors.toMap(m -> m, deliveryMethod -> {
return mockProcessingCtx.getProcessedTemplate(deliveryMethod, templateContext);
}));
preview.setProcessedTemplates(processedTemplates);
Map<UUID, Integer> recipientsCountByTarget = notificationRequest.getTargets().stream()
.collect(Collectors.toMap(id -> id, targetId -> {
return notificationTargetService.countRecipientsForNotificationTarget(user.getTenantId(), new NotificationTargetId(targetId));
}));
preview.setRecipientsCountByTarget(recipientsCountByTarget);
preview.setTotalRecipientsCount(recipientsCountByTarget.values().stream().mapToInt(Integer::intValue).sum());
return preview;
}
@GetMapping("/notification/request/{id}") @GetMapping("/notification/request/{id}")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
public NotificationRequest getNotificationRequestById(@PathVariable UUID id) throws ThingsboardException { public NotificationRequest getNotificationRequestById(@PathVariable UUID id) throws ThingsboardException {

9
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -120,7 +120,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} }
} }
notificationRequest.setStatus(NotificationRequestStatus.SENT); notificationRequest.setStatus(NotificationRequestStatus.PROCESSING);
NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); NotificationRequest savedNotificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
NotificationProcessingContext ctx = NotificationProcessingContext.builder() NotificationProcessingContext ctx = NotificationProcessingContext.builder()
@ -134,9 +134,9 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
Set<NotificationDeliveryMethod> deliveryMethods = ctx.getDeliveryMethods(); Set<NotificationDeliveryMethod> deliveryMethods = ctx.getDeliveryMethods();
List<ListenableFuture<Void>> results = new ArrayList<>(); List<ListenableFuture<Void>> results = new ArrayList<>();
for (NotificationTargetId targetId : notificationRequest.getTargets()) { for (UUID targetId : notificationRequest.getTargets()) {
DaoUtil.processBatches(pageLink -> { DaoUtil.processBatches(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTarget(tenantId, ctx.getCustomerId(), targetId, pageLink); return notificationTargetService.findRecipientsForNotificationTarget(tenantId, ctx.getCustomerId(), new NotificationTargetId(targetId), pageLink);
}, 200, recipientsBatch -> { }, 200, recipientsBatch -> {
for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) {
if (deliveryMethod.isIndependent()) continue; if (deliveryMethod.isIndependent()) continue;
@ -172,7 +172,8 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
Futures.whenAllComplete(results).run(() -> { Futures.whenAllComplete(results).run(() -> {
NotificationRequestStats stats = ctx.getStats(); NotificationRequestStats stats = ctx.getStats();
try { try {
notificationRequestService.updateNotificationRequestStats(tenantId, savedNotificationRequest.getId(), stats); notificationRequestService.updateNotificationRequest(tenantId, savedNotificationRequest.getId(),
NotificationRequestStatus.SENT, stats);
} 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);
} }

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

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.info.AlarmOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
@ -46,6 +47,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.NotificationExecutorService; import org.thingsboard.server.service.executors.NotificationExecutorService;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Service @Service
@TbCoreComponent @TbCoreComponent
@ -131,7 +133,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
NotificationInfo notificationInfo = constructNotificationInfo(alarm); NotificationInfo notificationInfo = constructNotificationInfo(alarm);
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId) .tenantId(tenantId)
.targets(List.of(targetId)) .targets(List.of(targetId).stream().map(UUIDBased::getId).collect(Collectors.toList()))
.templateId(notificationRule.getTemplateId()) .templateId(notificationRule.getTemplateId())
.additionalConfig(config) .additionalConfig(config)
.info(notificationInfo) .info(notificationInfo)

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

@ -90,7 +90,7 @@ public class NotificationProcessingContext {
return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod); return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod);
} }
protected <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) { public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) {
if (request.getInfo() != null) { if (request.getInfo() != null) {
templateContext = new HashMap<>(templateContext); templateContext = new HashMap<>(templateContext);
templateContext.putAll(request.getInfo().getTemplateData()); templateContext.putAll(request.getInfo().getTemplateData());

5
application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
@ -53,6 +54,7 @@ import java.net.URISyntaxException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -107,8 +109,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
UserOriginatedNotificationInfo notificationInfo = new UserOriginatedNotificationInfo(); UserOriginatedNotificationInfo notificationInfo = new UserOriginatedNotificationInfo();
notificationInfo.setDescription("My description"); notificationInfo.setDescription("My description");
NotificationRequest notificationRequest = NotificationRequest.builder() NotificationRequest notificationRequest = NotificationRequest.builder()
.tenantId(tenantId) .targets(targets.stream().map(UUIDBased::getId).collect(Collectors.toList()))
.targets(targets)
.templateId(notificationTemplateId) .templateId(notificationTemplateId)
.info(notificationInfo) .info(notificationInfo)
.additionalConfig(config) .additionalConfig(config)

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

@ -28,6 +28,8 @@ import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
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.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
@ -35,14 +37,21 @@ import org.thingsboard.server.common.data.notification.info.UserOriginatedNotifi
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; 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.AllUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.CustomerUsersNotificationTargetConfig;
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.UserListNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
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.PushDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SlackConversation; import org.thingsboard.server.common.data.notification.template.SlackConversation;
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.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;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationDao; import org.thingsboard.server.dao.notification.NotificationDao;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
@ -59,6 +68,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -271,7 +281,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test @Test
public void testNotificationUpdatesForALotOfUsers() throws Exception { public void testNotificationUpdatesForALotOfUsers() throws Exception {
int usersCount = 200; // FIXME: sometimes if set e.g. to 150, up to 5 WS sessions don't receive update int usersCount = 100;
Map<User, NotificationApiWsClient> sessions = new HashMap<>(); Map<User, NotificationApiWsClient> sessions = new HashMap<>();
List<NotificationTargetId> targets = new ArrayList<>(); List<NotificationTargetId> targets = new ArrayList<>();
@ -321,7 +331,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
}); });
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.until(() -> getStats(notificationRequest.getId()) != null); .until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId()); NotificationRequestStats stats = getStats(notificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(usersCount); assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(usersCount);
assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(usersCount); assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(usersCount);
@ -338,6 +348,117 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
sessions.values().forEach(WebSocketClient::close); sessions.values().forEach(WebSocketClient::close);
} }
@Test
public void testNotificationRequestPreview() throws Exception {
NotificationTarget target1 = new NotificationTarget();
target1.setName("Me");
UserListNotificationTargetConfig target1Config = new UserListNotificationTargetConfig();
target1Config.setUsersIds(DaoUtil.toUUIDs(List.of(tenantAdminUserId)));
target1.setConfiguration(target1Config);
target1 = saveNotificationTarget(target1);
createDifferentCustomer();
loginTenantAdmin();
int customerUsersCount = 10;
for (int i = 0; i < customerUsersCount; i++) {
User customerUser = new User();
customerUser.setAuthority(Authority.CUSTOMER_USER);
customerUser.setTenantId(tenantId);
customerUser.setCustomerId(differentCustomerId);
customerUser.setEmail("other-customer-" + i + "@thingsboard.org");
customerUser = createUser(customerUser, "12345678");
}
NotificationTarget target2 = new NotificationTarget();
target2.setName("Other customer users");
CustomerUsersNotificationTargetConfig target2Config = new CustomerUsersNotificationTargetConfig();
target2Config.setCustomerId(differentCustomerId.getId());
target2Config.setGetCustomerIdFromOriginatorEntity(false);
target2.setConfiguration(target2Config);
target2 = saveNotificationTarget(target2);
NotificationTemplate notificationTemplate = new NotificationTemplate();
notificationTemplate.setNotificationType(NotificationType.GENERAL);
notificationTemplate.setName("Test template");
String requestorEmail = TENANT_ADMIN_EMAIL;
NotificationTemplateConfig templateConfig = new NotificationTemplateConfig();
templateConfig.setDefaultTextTemplate("Default message for SMS and PUSH: ${email}");
templateConfig.setNotificationSubject("Default subject for EMAIL: ${email}");
HashMap<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> templates = new HashMap<>();
templateConfig.setDeliveryMethodsTemplates(templates);
notificationTemplate.setConfiguration(templateConfig);
PushDeliveryMethodNotificationTemplate pushNotificationTemplate = new PushDeliveryMethodNotificationTemplate();
pushNotificationTemplate.setEnabled(true);
// using default message for push
pushNotificationTemplate.setSubject("Subject for PUSH: ${email}");
templates.put(NotificationDeliveryMethod.PUSH, pushNotificationTemplate);
SmsDeliveryMethodNotificationTemplate smsNotificationTemplate = new SmsDeliveryMethodNotificationTemplate();
smsNotificationTemplate.setEnabled(true);
// using default message for sms
templates.put(NotificationDeliveryMethod.SMS, smsNotificationTemplate);
EmailDeliveryMethodNotificationTemplate emailNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
emailNotificationTemplate.setEnabled(true);
emailNotificationTemplate.setBody("Message for EMAIL: ${email}");
// using default subject for email
templates.put(NotificationDeliveryMethod.EMAIL, emailNotificationTemplate);
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
slackNotificationTemplate.setEnabled(true);
slackNotificationTemplate.setConversationType(SlackConversation.Type.PUBLIC_CHANNEL);
slackNotificationTemplate.setConversationId("U1234567");
slackNotificationTemplate.setBody("Message for SLACK: ${email}");
templates.put(NotificationDeliveryMethod.SLACK, slackNotificationTemplate);
notificationTemplate = saveNotificationTemplate(notificationTemplate);
NotificationRequest notificationRequest = new NotificationRequest();
notificationRequest.setTargets(List.of(target1.getUuidId(), target2.getUuidId()));
notificationRequest.setTemplateId(notificationTemplate.getId());
notificationRequest.setAdditionalConfig(new NotificationRequestConfig());
NotificationRequestPreview preview = doPost("/api/notification/request/preview", notificationRequest, NotificationRequestPreview.class);
assertThat(preview.getRecipientsCountByTarget().get(target1.getUuidId())).isEqualTo(1);
assertThat(preview.getRecipientsCountByTarget().get(target2.getUuidId())).isEqualTo(customerUsersCount);
assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount);
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = preview.getProcessedTemplates();
assertThat(processedTemplates.get(NotificationDeliveryMethod.PUSH)).asInstanceOf(type(PushDeliveryMethodNotificationTemplate.class))
.satisfies(template -> {
assertThat(template.getBody())
.startsWith("Default message for SMS and PUSH")
.endsWith(requestorEmail);
assertThat(template.getSubject())
.startsWith("Subject for PUSH")
.endsWith(requestorEmail);
});
assertThat(processedTemplates.get(NotificationDeliveryMethod.SMS)).asInstanceOf(type(SmsDeliveryMethodNotificationTemplate.class))
.satisfies(template -> {
assertThat(template.getBody())
.startsWith("Default message for SMS and PUSH")
.endsWith(requestorEmail);
});
assertThat(processedTemplates.get(NotificationDeliveryMethod.EMAIL)).asInstanceOf(type(EmailDeliveryMethodNotificationTemplate.class))
.satisfies(template -> {
assertThat(template.getBody())
.startsWith("Message for EMAIL")
.endsWith(requestorEmail);
assertThat(template.getSubject())
.startsWith("Default subject for EMAIL")
.endsWith(requestorEmail);
});
assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class))
.satisfies(template -> {
assertThat(template.getBody())
.startsWith("Message for SLACK")
.endsWith(requestorEmail);
});
}
@Test @Test
public void testNotificationRequestStats() throws Exception { public void testNotificationRequestStats() throws Exception {
wsClient.subscribeForUnreadNotifications(10); wsClient.subscribeForUnreadNotifications(10);
@ -350,7 +471,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
wsClient.waitForUpdate(); wsClient.waitForUpdate();
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.until(() -> getStats(notificationRequest.getId()) != null); .until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId()); NotificationRequestStats stats = getStats(notificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(1); assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(1);
@ -434,7 +555,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationRequest successfulNotificationRequest = submitNotificationRequest(Collections.emptyList(), notificationTemplate.getId(), 0); NotificationRequest successfulNotificationRequest = submitNotificationRequest(Collections.emptyList(), notificationTemplate.getId(), 0);
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.until(() -> getStats(successfulNotificationRequest.getId()) != null); .until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent());
verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq(config.getDefaultTextTemplate())); verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq(config.getDefaultTextTemplate()));
NotificationRequestStats stats = getStats(successfulNotificationRequest.getId()); NotificationRequestStats stats = getStats(successfulNotificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.SLACK)).hasValue(1); assertThat(stats.getSent().get(NotificationDeliveryMethod.SLACK)).hasValue(1);
@ -443,7 +564,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
doThrow(new RuntimeException(errorMessage)).when(slackService).sendMessage(any(), any(), any(), any()); doThrow(new RuntimeException(errorMessage)).when(slackService).sendMessage(any(), any(), any(), any());
NotificationRequest failedNotificationRequest = submitNotificationRequest(Collections.emptyList(), notificationTemplate.getId(), 0); NotificationRequest failedNotificationRequest = submitNotificationRequest(Collections.emptyList(), notificationTemplate.getId(), 0);
await().atMost(2, TimeUnit.SECONDS) await().atMost(2, TimeUnit.SECONDS)
.until(() -> getStats(failedNotificationRequest.getId()) != null); .until(() -> findNotificationRequest(failedNotificationRequest.getId()).isSent());
stats = getStats(failedNotificationRequest.getId()); stats = getStats(failedNotificationRequest.getId());
assertThat(stats.getErrors().get(NotificationDeliveryMethod.SLACK).values()).containsExactly(errorMessage); assertThat(stats.getErrors().get(NotificationDeliveryMethod.SLACK).values()).containsExactly(errorMessage);
} }

2
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java

@ -44,6 +44,6 @@ public interface NotificationRequestService {
PageData<NotificationRequest> findScheduledNotificationRequests(PageLink pageLink); PageData<NotificationRequest> findScheduledNotificationRequests(PageLink pageLink);
void updateNotificationRequestStats(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationRequestStats stats); void updateNotificationRequest(TenantId tenantId, NotificationRequestId requestId, NotificationRequestStatus requestStatus, NotificationRequestStats stats);
} }

4
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java

@ -32,7 +32,9 @@ public interface NotificationTargetService {
PageData<NotificationTarget> findNotificationTargetsByTenantId(TenantId tenantId, PageLink pageLink); PageData<NotificationTarget> findNotificationTargetsByTenantId(TenantId tenantId, PageLink pageLink);
PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId notificationTargetId, PageLink pageLink); PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink);
int countRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId targetId);
PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, CustomerId customerId, NotificationTargetConfig targetConfig, PageLink pageLink); PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, CustomerId customerId, NotificationTargetConfig targetConfig, PageLink pageLink);

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

@ -27,16 +27,15 @@ import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.info.NotificationInfo; import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import java.util.UUID;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -47,7 +46,7 @@ public class NotificationRequest extends BaseData<NotificationRequestId> impleme
private TenantId tenantId; private TenantId tenantId;
@NotNull @NotNull
private List<NotificationTargetId> targets; private List<UUID> targets;
@NotNull @NotNull
private NotificationTemplateId templateId; private NotificationTemplateId templateId;

31
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestPreview.java

@ -0,0 +1,31 @@
/**
* 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;
import lombok.Data;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import java.util.Map;
import java.util.UUID;
@Data
public class NotificationRequestPreview {
private Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates;
private int totalRecipientsCount;
private Map<UUID, Integer> recipientsCountByTarget;
}

1
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStatus.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.data.notification; package org.thingsboard.server.common.data.notification;
public enum NotificationRequestStatus { public enum NotificationRequestStatus {
PROCESSING,
SENT, SENT,
SCHEDULED SCHEDULED
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/EmailDeliveryMethodNotificationTemplate.java

@ -18,11 +18,13 @@ package org.thingsboard.server.common.data.notification.template;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject {
private String subject; private String subject;

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/PushDeliveryMethodNotificationTemplate.java

@ -18,11 +18,13 @@ package org.thingsboard.server.common.data.notification.template;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PushDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { public class PushDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject {
private String subject; private String subject;

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SlackDeliveryMethodNotificationTemplate.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.notification.template;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
@ -25,6 +26,7 @@ import javax.validation.constraints.NotEmpty;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate { public class SlackDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate {
private SlackConversation.Type conversationType; private SlackConversation.Type conversationType;

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/template/SmsDeliveryMethodNotificationTemplate.java

@ -18,11 +18,13 @@ package org.thingsboard.server.common.data.notification.template;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SmsDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate { public class SmsDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate {
public SmsDeliveryMethodNotificationTemplate(SmsDeliveryMethodNotificationTemplate other) { public SmsDeliveryMethodNotificationTemplate(SmsDeliveryMethodNotificationTemplate other) {

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

@ -109,7 +109,7 @@ public class NotificationRequestEntity extends BaseSqlEntity<NotificationRequest
notificationRequest.setId(new NotificationRequestId(id)); notificationRequest.setId(new NotificationRequestId(id));
notificationRequest.setCreatedTime(createdTime); notificationRequest.setCreatedTime(createdTime);
notificationRequest.setTenantId(getTenantId(tenantId)); notificationRequest.setTenantId(getTenantId(tenantId));
notificationRequest.setTargets(listFromString(targets, uuid -> new NotificationTargetId(UUID.fromString(uuid)))); notificationRequest.setTargets(listFromString(targets, UUID::fromString));
notificationRequest.setTemplateId(getEntityId(templateId, NotificationTemplateId::new)); notificationRequest.setTemplateId(getEntityId(templateId, NotificationTemplateId::new));
notificationRequest.setInfo(fromJson(info, NotificationInfo.class)); notificationRequest.setInfo(fromJson(info, NotificationInfo.class));
notificationRequest.setAdditionalConfig(fromJson(additionalConfig, NotificationRequestConfig.class)); notificationRequest.setAdditionalConfig(fromJson(additionalConfig, NotificationRequestConfig.class));

4
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java

@ -79,8 +79,8 @@ public class DefaultNotificationRequestService implements NotificationRequestSer
} }
@Override @Override
public void updateNotificationRequestStats(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationRequestStats stats) { public void updateNotificationRequest(TenantId tenantId, NotificationRequestId requestId, NotificationRequestStatus requestStatus, NotificationRequestStats stats) {
notificationRequestDao.updateStatsById(tenantId, notificationRequestId, stats); notificationRequestDao.updateById(tenantId, requestId, requestStatus, stats);
} }

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

@ -62,13 +62,18 @@ public class DefaultNotificationTargetService implements NotificationTargetServi
} }
@Override @Override
public PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId notificationTargetId, PageLink pageLink) { public PageData<User> findRecipientsForNotificationTarget(TenantId tenantId, CustomerId customerId, NotificationTargetId targetId, PageLink pageLink) {
NotificationTarget notificationTarget = findNotificationTargetById(tenantId, notificationTargetId); NotificationTarget notificationTarget = findNotificationTargetById(tenantId, targetId);
Objects.requireNonNull(notificationTarget, "Notification target [" + notificationTargetId + "] not found"); Objects.requireNonNull(notificationTarget, "Notification target [" + targetId + "] not found");
NotificationTargetConfig configuration = notificationTarget.getConfiguration(); NotificationTargetConfig configuration = notificationTarget.getConfiguration();
return findRecipientsForNotificationTargetConfig(tenantId, customerId, configuration, pageLink); return findRecipientsForNotificationTargetConfig(tenantId, customerId, configuration, pageLink);
} }
@Override
public int countRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId targetId) {
return (int) findRecipientsForNotificationTarget(tenantId, null, targetId, new PageLink(1)).getTotalElements();
}
@Override @Override
public PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, CustomerId customerId, NotificationTargetConfig targetConfig, PageLink pageLink) { public PageData<User> findRecipientsForNotificationTargetConfig(TenantId tenantId, CustomerId customerId, NotificationTargetConfig targetConfig, PageLink pageLink) {
switch (targetConfig.getType()) { switch (targetConfig.getType()) {

2
dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java

@ -41,7 +41,7 @@ public interface NotificationRequestDao extends Dao<NotificationRequest> {
PageData<NotificationRequest> findAllByStatus(NotificationRequestStatus status, PageLink pageLink); PageData<NotificationRequest> findAllByStatus(NotificationRequestStatus status, PageLink pageLink);
void updateStatsById(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationRequestStats stats); void updateById(TenantId tenantId, NotificationRequestId requestId, NotificationRequestStatus requestStatus, NotificationRequestStats stats);
boolean existsByStatusAndTargetId(TenantId tenantId, NotificationRequestStatus status, NotificationTargetId targetId); boolean existsByStatusAndTargetId(TenantId tenantId, NotificationRequestStatus status, NotificationTargetId targetId);

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

@ -72,8 +72,8 @@ public class JpaNotificationRequestDao extends JpaAbstractDao<NotificationReques
} }
@Override @Override
public void updateStatsById(TenantId tenantId, NotificationRequestId notificationRequestId, NotificationRequestStats stats) { public void updateById(TenantId tenantId, NotificationRequestId requestId, NotificationRequestStatus requestStatus, NotificationRequestStats stats) {
notificationRequestRepository.updateStatsById(notificationRequestId.getId(), JacksonUtil.valueToTree(stats)); notificationRequestRepository.updateStatusAndStatsById(requestId.getId(), requestStatus, JacksonUtil.valueToTree(stats));
} }
@Override @Override

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

@ -46,8 +46,10 @@ public interface NotificationRequestRepository extends JpaRepository<Notificatio
@Modifying @Modifying
@Transactional @Transactional
@Query("UPDATE NotificationRequestEntity r SET r.stats = :stats WHERE r.id = :id") @Query("UPDATE NotificationRequestEntity r SET r.status = :status, r.stats = :stats WHERE r.id = :id")
void updateStatsById(@Param("id") UUID id, @Param("stats") JsonNode stats); void updateStatusAndStatsById(@Param("id") UUID id,
@Param("status") NotificationRequestStatus status,
@Param("stats") JsonNode stats);
boolean existsByStatusAndTargetsContaining(NotificationRequestStatus status, String targetIdStr); boolean existsByStatusAndTargetsContaining(NotificationRequestStatus status, String targetIdStr);

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

@ -24,12 +24,13 @@ import org.thingsboard.server.common.data.notification.NotificationRequestConfig
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.List; import java.util.List;
import java.util.UUID;
@Data @Data
public class TbNotificationNodeConfiguration implements NodeConfiguration<TbNotificationNodeConfiguration> { public class TbNotificationNodeConfiguration implements NodeConfiguration<TbNotificationNodeConfiguration> {
@NotEmpty @NotEmpty
private List<NotificationTargetId> targets; private List<UUID> targets;
@NotNull @NotNull
private NotificationTemplateId templateId; private NotificationTemplateId templateId;
private NotificationRequestConfig additionalConfig; private NotificationRequestConfig additionalConfig;

Loading…
Cancel
Save