Browse Source

Notification system improvements

pull/7911/head
ViacheslavKlimov 3 years ago
parent
commit
79670d9f9d
  1. 9
      application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java
  2. 13
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  3. 6
      application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java
  4. 3
      application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java
  5. 4
      application/src/main/java/org/thingsboard/server/service/notification/rule/DefaultNotificationRuleProcessingService.java
  6. 6
      application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
  7. 6
      application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
  8. 3
      application/src/main/resources/thingsboard.yml
  9. 33
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  10. 1
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  11. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestService.java
  12. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  13. 1
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  14. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java
  15. 5
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestStats.java
  16. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRule.java
  17. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/template/NotificationTemplate.java
  18. 1
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
  19. 53
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRequestService.java
  20. 17
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java
  21. 6
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java
  22. 21
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  23. 19
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java
  24. 4
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java
  25. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRuleDao.java
  26. 43
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCacheKey.java
  27. 38
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCacheValue.java
  28. 32
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCaffeineCache.java
  29. 35
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestRedisCache.java
  30. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCaffeineCache.java
  31. 2
      dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleRedisCache.java
  32. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java
  33. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRuleDao.java
  34. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java
  35. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java
  36. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbNotificationNode.java
  37. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/notification/TbSlackNode.java
  38. 1
      ui-ngx/src/app/shared/decorators/coerce-boolean.ts

9
application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java

@ -68,15 +68,6 @@ public class NotificationTargetController extends BaseController {
@ApiOperation(value = "Save notification target (saveNotificationTarget)", @ApiOperation(value = "Save notification target (saveNotificationTarget)",
notes = "Create or update notification target.\n\n" + notes = "Create or update notification target.\n\n" +
// "Examples with different configuration types:\n" +
// "- USER_LIST:\n" +
// "```\n{\n \"name\": \"Special users\",\n \"configuration\": {\n \"type\": \"USER_LIST\",\n \"usersIds\": [\n \"ea31a460-3d85-11ed-9200-77fc04fa14fa\",\n \"86f7b260-3d88-11ed-ad72-ad2ee0f70ba1\"\n ]\n }\n}\n```\n" +
// "- CUSTOMER_USERS (not accessible to system administrator):\n" +
// "```\n{\n \"name\": \"Users of my customer\",\n \"configuration\": {\n \"type\": \"CUSTOMER_USERS\",\n \"customerId\": \"ea31a460-3d85-11ed-9200-77fc04fa14fa\"\n }\n}\n```\n" +
// "or if you would like to use the target in notification rule and get customerId from alarm:\n" +
// "```\n{\n \"name\": \"Alarm's customer users\",\n \"configuration\": {\n \"type\": \"CUSTOMER_USERS\",\n \"customerId\": null,\n \"getCustomerIdFromOriginatorEntity\": true\n }\n}\n```\n" +
// "- ALL_USERS:\n" +
// "```\n{\n \"name\": \"All my users\",\n \"configuration\": {\n \"type\": \"ALL_USERS\"\n }\n}\n```\n\n" +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/target") @PostMapping("/target")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")

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

@ -172,7 +172,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
} else { } else {
int failures = stats.getErrors().values().stream().mapToInt(Map::size).sum(); int failures = stats.getErrors().values().stream().mapToInt(Map::size).sum();
sendBasicNotification(tenantId, senderId, "Notification failure", sendBasicNotification(tenantId, senderId, "Notification failure",
"Some notifications were not sent (" + failures + ")"); // TODO: 'Go to request' button "Some notifications were not sent (" + failures + ")"); // TODO: 'Go to' button
} }
} }
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
@ -333,10 +333,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
public NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public NotificationRequest updateNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
log.debug("Updating notification request {}", notificationRequest.getId()); log.debug("Updating notification request {}", notificationRequest.getId());
notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest); notificationRequest = notificationRequestService.saveNotificationRequest(tenantId, notificationRequest);
// marking related notifications as unread: FIXME: causes each subscription to fetch notifications on each request update // marking related notifications as unread: TODO: causes each subscription to fetch notifications on each request update
notificationService.updateNotificationsStatusByRequestId(tenantId, notificationRequest.getId(), NotificationStatus.SENT); notificationService.updateNotificationsStatusByRequestId(tenantId, notificationRequest.getId(), NotificationStatus.SENT);
// TODO: no need to update request with other than PLATFORM_USERS target type // TODO: no need to send request update for other than PLATFORM_USERS target type
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequest.getId()) .notificationRequestId(notificationRequest.getId())
.notificationInfo(notificationRequest.getInfo()) .notificationInfo(notificationRequest.getInfo())
@ -348,9 +348,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override @Override
public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) { public void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId) {
log.debug("Deleting notification request {}", notificationRequestId); log.debug("Deleting notification request {}", notificationRequestId);
NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId);// TODO: add caching NotificationRequest notificationRequest = notificationRequestService.findNotificationRequestById(tenantId, notificationRequestId);
notificationRequestService.deleteNotificationRequestById(tenantId, notificationRequestId); notificationRequestService.deleteNotificationRequest(tenantId, notificationRequest);
// todo: check delivery method ?
// TODO: no need to send request update for other than PLATFORM_USERS target type
if (notificationRequest.isSent()) { if (notificationRequest.isSent()) {
onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder() onNotificationRequestUpdate(tenantId, NotificationRequestUpdate.builder()
.notificationRequestId(notificationRequestId) .notificationRequestId(notificationRequestId)

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

@ -15,12 +15,10 @@
*/ */
package org.thingsboard.server.service.notification.channels; package org.thingsboard.server.service.notification.channels;
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;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.notification.AlreadySentException;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationProcessingContext; import org.thingsboard.server.common.data.notification.NotificationProcessingContext;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
@ -37,10 +35,6 @@ public class SlackNotificationChannel implements NotificationChannel<SlackConver
@Override @Override
public ListenableFuture<Void> sendNotification(SlackConversation conversation, SlackDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { public ListenableFuture<Void> sendNotification(SlackConversation conversation, SlackDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
if (ctx.getStats().contains(NotificationDeliveryMethod.SLACK)) {
return Futures.immediateFailedFuture(new AlreadySentException());
}
SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK); SlackNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.SLACK);
return executor.submit(() -> { return executor.submit(() -> {
slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), conversation.getId(), processedTemplate.getBody()); slackService.sendMessage(ctx.getTenantId(), config.getBotToken(), conversation.getId(), processedTemplate.getBody());

3
application/src/main/java/org/thingsboard/server/service/notification/channels/SmsNotificationChannel.java

@ -37,8 +37,9 @@ public class SmsNotificationChannel implements NotificationChannel<User, SmsDeli
@Override @Override
public ListenableFuture<Void> sendNotification(User recipient, SmsDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { public ListenableFuture<Void> sendNotification(User recipient, SmsDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) {
String phone = recipient.getPhone(); String phone = recipient.getPhone();
if (StringUtils.isBlank(phone)) if (StringUtils.isBlank(phone)) {
return Futures.immediateFailedFuture(new RuntimeException("User does not have phone number")); 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}, processedTemplate.getBody()); smsService.sendSms(recipient.getTenantId(), recipient.getCustomerId(), new String[]{phone}, processedTemplate.getBody());

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

@ -122,7 +122,9 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
processNotificationRule(rule, originatorEntityId, triggerObject); processNotificationRule(rule, originatorEntityId, triggerObject);
}); });
} }
}, e -> {}); }, e -> {
log.error("Failed to find notification rules by trigger type {}", triggerType, e);
});
} }
private void processNotificationRule(NotificationRule rule, EntityId originatorEntityId, Object triggerObject) { private void processNotificationRule(NotificationRule rule, EntityId originatorEntityId, Object triggerObject) {

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

@ -55,7 +55,7 @@ public class DefaultSlackService implements SlackService {
.expireAfterWrite(20, TimeUnit.SECONDS) .expireAfterWrite(20, TimeUnit.SECONDS)
.maximumSize(100) .maximumSize(100)
.build(); .build();
private static final int CONVERSATIONS_LIMIT = 1000; private static final int CONVERSATIONS_LOAD_LIMIT = 1000;
@Override @Override
public void sendMessage(TenantId tenantId, String token, String conversationId, String message) { public void sendMessage(TenantId tenantId, String token, String conversationId, String message) {
@ -71,7 +71,7 @@ public class DefaultSlackService implements SlackService {
return cache.get(conversationType + ":" + token, k -> { return cache.get(conversationType + ":" + token, k -> {
if (conversationType == SlackConversationType.DIRECT) { if (conversationType == SlackConversationType.DIRECT) {
UsersListRequest request = UsersListRequest.builder() UsersListRequest request = UsersListRequest.builder()
.limit(CONVERSATIONS_LIMIT) .limit(CONVERSATIONS_LOAD_LIMIT)
.build(); .build();
UsersListResponse response = sendRequest(token, request, MethodsClient::usersList); UsersListResponse response = sendRequest(token, request, MethodsClient::usersList);
@ -89,7 +89,7 @@ public class DefaultSlackService implements SlackService {
.types(List.of(conversationType == SlackConversationType.PUBLIC_CHANNEL ? .types(List.of(conversationType == SlackConversationType.PUBLIC_CHANNEL ?
ConversationType.PUBLIC_CHANNEL : ConversationType.PUBLIC_CHANNEL :
ConversationType.PRIVATE_CHANNEL)) ConversationType.PRIVATE_CHANNEL))
.limit(CONVERSATIONS_LIMIT) .limit(CONVERSATIONS_LOAD_LIMIT)
.excludeArchived(true) .excludeArchived(true)
.build(); .build();

6
application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java

@ -252,7 +252,11 @@ public class DefaultWebSocketService implements WebSocketService {
if (cmd != null) { if (cmd != null) {
String sessionId = sessionRef.getSessionId(); String sessionId = sessionRef.getSessionId();
if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) { if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)) {
cmdHandler.handle(sessionRef, cmd); // todo: handle exceptions try {
cmdHandler.handle(sessionRef, cmd);
} catch (Exception e) {
log.error("Failed to handle WS cmd: {}", cmd, e);
}
} }
} }
} }

3
application/src/main/resources/thingsboard.yml

@ -438,6 +438,9 @@ cache:
notificationRules: notificationRules:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}" timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}"
maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:10000}" maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:10000}"
notificationRequests:
timeToLiveInMinutes: "${CACHE_SPECS_NOTIFICATION_RULES_TTL:1440}"
maxSize: "${CACHE_SPECS_NOTIFICATION_RULES_MAX_SIZE:10000}"
attributes: attributes:
timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}" timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}" maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"

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

@ -38,7 +38,6 @@ 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.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
@ -172,16 +171,12 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test @Test
public void testMarkingAsRead_multipleSessions() throws Exception { public void testMarkingAsRead_multipleSessions() throws Exception {
connectOtherWsClient(); connectOtherWsClient();
wsClient.subscribeForUnreadNotifications(10); wsClient.subscribeForUnreadNotifications(10).waitForReply(true);
otherWsClient.subscribeForUnreadNotifications(10); otherWsClient.subscribeForUnreadNotifications(10).waitForReply(true);
wsClient.waitForReply(true);
otherWsClient.waitForReply(true);
otherWsClient.subscribeForUnreadNotificationsCount();
otherWsClient.waitForReply(true);
NotificationTarget notificationTarget = createNotificationTarget(customerUserId); NotificationTarget notificationTarget = createNotificationTarget(customerUserId);
wsClient.registerWaitForUpdate(); wsClient.registerWaitForUpdate();
otherWsClient.registerWaitForUpdate(2); otherWsClient.registerWaitForUpdate();
String notificationText1 = "Notification 1"; String notificationText1 = "Notification 1";
submitNotificationRequest(notificationTarget.getId(), notificationText1); submitNotificationRequest(notificationTarget.getId(), notificationText1);
wsClient.waitForUpdate(true); wsClient.waitForUpdate(true);
@ -189,24 +184,22 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
Notification notification1 = wsClient.getLastDataUpdate().getUpdate(); Notification notification1 = wsClient.getLastDataUpdate().getUpdate();
wsClient.registerWaitForUpdate(); wsClient.registerWaitForUpdate();
otherWsClient.registerWaitForUpdate(2); otherWsClient.registerWaitForUpdate();
String notificationText2 = "Notification 2"; String notificationText2 = "Notification 2";
submitNotificationRequest(notificationTarget.getId(), notificationText2); submitNotificationRequest(notificationTarget.getId(), notificationText2);
wsClient.waitForUpdate(true); wsClient.waitForUpdate(true);
otherWsClient.waitForUpdate(true); otherWsClient.waitForUpdate(true);
assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(2); assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(2);
assertThat(otherWsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(2); assertThat(otherWsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(2);
assertThat(otherWsClient.getLastCountUpdate().getTotalUnreadCount()).isEqualTo(2);
wsClient.registerWaitForUpdate(); wsClient.registerWaitForUpdate();
otherWsClient.registerWaitForUpdate(2); otherWsClient.registerWaitForUpdate();
wsClient.markNotificationAsRead(notification1.getUuidId()); wsClient.markNotificationAsRead(notification1.getUuidId());
wsClient.waitForUpdate(true); wsClient.waitForUpdate(true);
otherWsClient.waitForUpdate(true); otherWsClient.waitForUpdate(true);
checkFullNotificationsUpdate(wsClient.getLastDataUpdate(), notificationText2); checkFullNotificationsUpdate(wsClient.getLastDataUpdate(), notificationText2);
checkFullNotificationsUpdate(otherWsClient.getLastDataUpdate(), notificationText2); checkFullNotificationsUpdate(otherWsClient.getLastDataUpdate(), notificationText2);
assertThat(otherWsClient.getLastCountUpdate().getTotalUnreadCount()).isOne();
} }
@Test @Test
@ -300,8 +293,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
} }
@Test @Test
public void testNotificationUpdatesForALotOfUsers() throws Exception { public void testNotificationUpdatesForSeveralUsers() throws Exception {
int usersCount = 80; int usersCount = 150;
Map<User, NotificationApiWsClient> sessions = new HashMap<>(); Map<User, NotificationApiWsClient> sessions = new HashMap<>();
List<NotificationTargetId> targets = new ArrayList<>(); List<NotificationTargetId> targets = new ArrayList<>();
@ -317,23 +310,21 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTarget notificationTarget = createNotificationTarget(user.getId()); NotificationTarget notificationTarget = createNotificationTarget(user.getId());
targets.add(notificationTarget.getId()); targets.add(notificationTarget.getId());
wsClient.registerWaitForUpdate(2); wsClient.registerWaitForUpdate();
wsClient.subscribeForUnreadNotifications(10); wsClient.subscribeForUnreadNotifications(10);
wsClient.subscribeForUnreadNotificationsCount();
} }
sessions.values().forEach(wsClient -> wsClient.waitForUpdate(true)); sessions.values().forEach(wsClient -> wsClient.waitForUpdate(true));
loginTenantAdmin(); loginTenantAdmin();
sessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate(2)); sessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate());
NotificationRequest notificationRequest = submitNotificationRequest(targets, "Hello, ${recipientEmail}", 0, NotificationRequest notificationRequest = submitNotificationRequest(targets, "Hello, ${recipientEmail}", 0,
NotificationDeliveryMethod.PUSH); NotificationDeliveryMethod.PUSH);
await().atMost(10, TimeUnit.SECONDS) await().atMost(10, TimeUnit.SECONDS)
.pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS) .pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS)
.until(() -> { .until(() -> {
long receivedUpdate = sessions.values().stream() long receivedUpdate = sessions.values().stream()
.filter(wsClient -> wsClient.getLastDataUpdate() != null .filter(wsClient -> wsClient.getLastDataUpdate() != null)
&& wsClient.getLastCountUpdate() != null)
.count(); .count();
System.err.println("WS sessions received update: " + receivedUpdate); System.err.println("WS sessions received update: " + receivedUpdate);
return receivedUpdate == sessions.size(); return receivedUpdate == sessions.size();
@ -341,7 +332,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
sessions.forEach((user, wsClient) -> { sessions.forEach((user, wsClient) -> {
assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isOne(); assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isOne();
assertThat(wsClient.getLastCountUpdate().getTotalUnreadCount()).isOne();
Notification notification = wsClient.getLastDataUpdate().getUpdate(); Notification notification = wsClient.getLastDataUpdate().getUpdate();
assertThat(notification.getRecipientId()).isEqualTo(user.getId()); assertThat(notification.getRecipientId()).isEqualTo(user.getId());
@ -354,13 +344,12 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
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);
sessions.values().forEach(wsClient -> wsClient.registerWaitForUpdate(2)); sessions.values().forEach(wsClient -> wsClient.registerWaitForUpdate());
deleteNotificationRequest(notificationRequest.getId()); deleteNotificationRequest(notificationRequest.getId());
sessions.values().forEach(wsClient -> { sessions.values().forEach(wsClient -> {
wsClient.waitForUpdate(true); wsClient.waitForUpdate(true);
assertThat(wsClient.getLastDataUpdate().getNotifications()).isEmpty(); assertThat(wsClient.getLastDataUpdate().getNotifications()).isEmpty();
assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isZero(); assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isZero();
assertThat(wsClient.getLastCountUpdate().getTotalUnreadCount()).isZero();
}); });
sessions.values().forEach(WebSocketClient::close); sessions.values().forEach(WebSocketClient::close);

1
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -391,7 +391,6 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
DeviceProfileAlarm alarm = new DeviceProfileAlarm(); DeviceProfileAlarm alarm = new DeviceProfileAlarm();
alarm.setAlarmType(alarmType); alarm.setAlarmType(alarmType);
alarm.setId(alarmType); alarm.setId(alarmType);
alarm.setNotificationRuleId(notificationRuleId);
AlarmRule alarmRule = new AlarmRule(); AlarmRule alarmRule = new AlarmRule();
alarmRule.setAlarmDetails("Details"); alarmRule.setAlarmDetails("Details");
AlarmCondition alarmCondition = new AlarmCondition(); AlarmCondition alarmCondition = new AlarmCondition();

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

@ -45,7 +45,7 @@ public interface NotificationRequestService {
List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId); List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId);
void deleteNotificationRequestById(TenantId tenantId, NotificationRequestId id); void deleteNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest);
PageData<NotificationRequest> findScheduledNotificationRequests(PageLink pageLink); PageData<NotificationRequest> findScheduledNotificationRequests(PageLink pageLink);

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -30,6 +30,7 @@ public class CacheConstants {
public static final String TENANTS_EXIST_CACHE = "tenantsExist"; public static final String TENANTS_EXIST_CACHE = "tenantsExist";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String NOTIFICATION_RULES_CACHE = "notificationRules"; public static final String NOTIFICATION_RULES_CACHE = "notificationRules";
public static final String NOTIFICATION_REQUESTS_CACHE = "notificationRequests";
public static final String ASSET_PROFILE_CACHE = "assetProfiles"; public static final String ASSET_PROFILE_CACHE = "assetProfiles";
public static final String ATTRIBUTES_CACHE = "attributes"; public static final String ATTRIBUTES_CACHE = "attributes";

1
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java

@ -19,7 +19,6 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
@ -60,6 +59,4 @@ public class DeviceProfileAlarm implements Serializable {
"This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.") "This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.")
private List<String> propagateRelationTypes; private List<String> propagateRelationTypes;
private NotificationRuleId notificationRuleId;
} }

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

@ -20,7 +20,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import java.util.Collections; import java.util.Collections;
@ -70,10 +69,6 @@ public class NotificationRequestStats {
errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(key, errorMessage); errors.computeIfAbsent(deliveryMethod, k -> new ConcurrentHashMap<>()).put(key, errorMessage);
} }
public boolean contains(NotificationDeliveryMethod deliveryMethod) {
return sent.containsKey(deliveryMethod) || errors.containsKey(deliveryMethod);
}
public boolean contains(NotificationDeliveryMethod deliveryMethod, Object recipientId) { public boolean contains(NotificationDeliveryMethod deliveryMethod, Object recipientId) {
Set<Object> processedRecipients = this.processedRecipients.get(deliveryMethod); Set<Object> processedRecipients = this.processedRecipients.get(deliveryMethod);
return processedRecipients != null && processedRecipients.contains(recipientId); return processedRecipients != null && processedRecipients.contains(recipientId);

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

@ -50,7 +50,7 @@ public class NotificationRule extends BaseData<NotificationRuleId> implements Ha
private NotificationRuleTriggerConfig triggerConfig; private NotificationRuleTriggerConfig triggerConfig;
@NotNull @NotNull
@Valid @Valid
private NotificationRuleRecipientsConfig recipientsConfig; // todo: add pg_tgrm index (but index is 2.5x size of the column) private NotificationRuleRecipientsConfig recipientsConfig;
private NotificationRuleConfig additionalConfig; private NotificationRuleConfig additionalConfig;

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

@ -42,6 +42,6 @@ public class NotificationTemplate extends BaseData<NotificationTemplateId> imple
private NotificationType notificationType; private NotificationType notificationType;
@Valid @Valid
@NotNull @NotNull
private NotificationTemplateConfig configuration; // TODO: add pg_tgrm index private NotificationTemplateConfig configuration;
} }

1
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java

@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseEntity;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;

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

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
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.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -29,14 +30,19 @@ 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.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.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.notification.cache.NotificationRequestCacheKey;
import org.thingsboard.server.dao.notification.cache.NotificationRequestCacheValue;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import java.util.List; import java.util.List;
import java.util.Optional;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultNotificationRequestService implements NotificationRequestService { public class DefaultNotificationRequestService extends AbstractCachedEntityService<NotificationRequestCacheKey, NotificationRequestCacheValue, NotificationRequest> implements NotificationRequestService, EntityDaoService {
private final NotificationRequestDao notificationRequestDao; private final NotificationRequestDao notificationRequestDao;
@ -45,7 +51,14 @@ public class DefaultNotificationRequestService implements NotificationRequestSer
@Override @Override
public NotificationRequest saveNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { public NotificationRequest saveNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
notificationRequestValidator.validate(notificationRequest, NotificationRequest::getTenantId); notificationRequestValidator.validate(notificationRequest, NotificationRequest::getTenantId);
return notificationRequestDao.save(tenantId, notificationRequest); try {
notificationRequest = notificationRequestDao.save(tenantId, notificationRequest);
publishEvictEvent(notificationRequest);
} catch (Exception e) {
handleEvictEvent(notificationRequest);
throw e;
}
return notificationRequest;
} }
@Override @Override
@ -75,14 +88,21 @@ public class DefaultNotificationRequestService implements NotificationRequestSer
@Override @Override
public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) { public List<NotificationRequest> findNotificationRequestsByRuleIdAndOriginatorEntityId(TenantId tenantId, NotificationRuleId ruleId, EntityId originatorEntityId) {
// FIXME: add caching NotificationRequestCacheKey cacheKey = NotificationRequestCacheKey.builder()
return notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId); .originatorEntityId(originatorEntityId)
.ruleId(ruleId)
.build();
return cache.getAndPutInTransaction(cacheKey, () -> NotificationRequestCacheValue.builder()
.notificationRequests(notificationRequestDao.findByRuleIdAndOriginatorEntityId(tenantId, ruleId, originatorEntityId))
.build(), false)
.getNotificationRequests();
} }
// ON DELETE CASCADE is used: notifications for request are deleted as well // ON DELETE CASCADE is used: notifications for request are deleted as well
@Override @Override
public void deleteNotificationRequestById(TenantId tenantId, NotificationRequestId id) { public void deleteNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) {
notificationRequestDao.removeById(tenantId, id.getId()); publishEvictEvent(notificationRequest);
notificationRequestDao.removeById(tenantId, notificationRequest.getUuidId());
} }
@Override @Override
@ -100,6 +120,27 @@ public class DefaultNotificationRequestService implements NotificationRequestSer
notificationRequestDao.removeByTenantId(tenantId); notificationRequestDao.removeByTenantId(tenantId);
} }
@Override
public void handleEvictEvent(NotificationRequest notificationRequest) {
if (notificationRequest.getRuleId() == null) return;
NotificationRequestCacheKey cacheKey = NotificationRequestCacheKey.builder()
.originatorEntityId(notificationRequest.getOriginatorEntityId())
.ruleId(notificationRequest.getRuleId())
.build();
cache.evict(cacheKey);
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findNotificationRequestById(tenantId, new NotificationRequestId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_REQUEST;
}
private static class NotificationRequestValidator extends DataValidator<NotificationRequest> { private static class NotificationRequestValidator extends DataValidator<NotificationRequest> {

17
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java

@ -17,6 +17,9 @@ package org.thingsboard.server.dao.notification;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.NotificationRuleId; import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
@ -25,15 +28,17 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
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.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheKey; import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheKey;
import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheValue; import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheValue;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultNotificationRuleService extends AbstractCachedEntityService<NotificationRuleCacheKey, NotificationRuleCacheValue, NotificationRule> implements NotificationRuleService { public class DefaultNotificationRuleService extends AbstractCachedEntityService<NotificationRuleCacheKey, NotificationRuleCacheValue, NotificationRule> implements NotificationRuleService, EntityDaoService {
private final NotificationRuleDao notificationRuleDao; private final NotificationRuleDao notificationRuleDao;
@ -112,4 +117,14 @@ public class DefaultNotificationRuleService extends AbstractCachedEntityService<
cache.evict(cacheKey); cache.evict(cacheKey);
} }
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findNotificationRuleById(tenantId, new NotificationRuleId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_RULE;
}
} }

6
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationSettingsService.java

@ -93,6 +93,12 @@ public class DefaultNotificationSettingsService implements NotificationSettingsS
NotificationTemplate alarmNotificationTemplate = new NotificationTemplate(); NotificationTemplate alarmNotificationTemplate = new NotificationTemplate();
/*
* TODO:
* rule chain start failure
* alarm
* */
} }
} }

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

@ -18,8 +18,11 @@ package org.thingsboard.server.dao.notification;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
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.UserId; import org.thingsboard.server.common.data.id.UserId;
@ -34,17 +37,19 @@ import org.thingsboard.server.common.data.notification.targets.platform.UsersFil
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.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultNotificationTargetService extends AbstractEntityService implements NotificationTargetService { public class DefaultNotificationTargetService extends AbstractEntityService implements NotificationTargetService, EntityDaoService {
private final NotificationTargetDao notificationTargetDao; private final NotificationTargetDao notificationTargetDao;
private final NotificationRequestDao notificationRequestDao; private final NotificationRequestDao notificationRequestDao;
@ -133,10 +138,10 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
@Override @Override
public void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id) { public void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id) {
if (notificationRequestDao.existsByStatusAndTargetId(tenantId, NotificationRequestStatus.SCHEDULED, id)) { if (notificationRequestDao.existsByTenantIdAndStatusAndTargetId(tenantId, NotificationRequestStatus.SCHEDULED, id)) {
throw new IllegalArgumentException("Notification target is referenced by scheduled notification request"); throw new IllegalArgumentException("Notification target is referenced by scheduled notification request");
} }
if (notificationRuleDao.existsByTargetId(tenantId, id)) { if (notificationRuleDao.existsByTenantIdAndTargetId(tenantId, id)) {
throw new IllegalArgumentException("Notification target is being used in notification rule"); throw new IllegalArgumentException("Notification target is being used in notification rule");
} }
notificationTargetDao.removeById(tenantId, id.getId()); notificationTargetDao.removeById(tenantId, id.getId());
@ -147,4 +152,14 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
notificationTargetDao.removeByTenantId(tenantId); notificationTargetDao.removeByTenantId(tenantId);
} }
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findNotificationTargetById(tenantId, new NotificationTargetId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_TARGET;
}
} }

19
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java

@ -17,6 +17,9 @@ package org.thingsboard.server.dao.notification;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.NotificationTemplateId; import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
@ -25,13 +28,15 @@ import org.thingsboard.server.common.data.notification.template.NotificationTemp
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.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultNotificationTemplateService extends AbstractEntityService implements NotificationTemplateService { public class DefaultNotificationTemplateService extends AbstractEntityService implements NotificationTemplateService, EntityDaoService {
private final NotificationTemplateDao notificationTemplateDao; private final NotificationTemplateDao notificationTemplateDao;
private final NotificationRequestDao notificationRequestDao; private final NotificationRequestDao notificationRequestDao;
@ -60,7 +65,7 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im
@Override @Override
public void deleteNotificationTemplateById(TenantId tenantId, NotificationTemplateId id) { public void deleteNotificationTemplateById(TenantId tenantId, NotificationTemplateId id) {
if (notificationRequestDao.existsByStatusAndTemplateId(tenantId, NotificationRequestStatus.SCHEDULED, id)) { if (notificationRequestDao.existsByTenantIdAndStatusAndTemplateId(tenantId, NotificationRequestStatus.SCHEDULED, id)) {
throw new IllegalArgumentException("Notification template is referenced by scheduled notification request"); throw new IllegalArgumentException("Notification template is referenced by scheduled notification request");
} }
try { try {
@ -78,4 +83,14 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im
notificationTemplateDao.removeByTenantId(tenantId); notificationTemplateDao.removeByTenantId(tenantId);
} }
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findNotificationTemplateById(tenantId, new NotificationTemplateId(entityId.getId())));
}
@Override
public EntityType getEntityType() {
return EntityType.NOTIFICATION_TEMPLATE;
}
} }

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

@ -46,9 +46,9 @@ public interface NotificationRequestDao extends Dao<NotificationRequest> {
void updateById(TenantId tenantId, NotificationRequestId requestId, NotificationRequestStatus requestStatus, NotificationRequestStats stats); void updateById(TenantId tenantId, NotificationRequestId requestId, NotificationRequestStatus requestStatus, NotificationRequestStats stats);
boolean existsByStatusAndTargetId(TenantId tenantId, NotificationRequestStatus status, NotificationTargetId targetId); boolean existsByTenantIdAndStatusAndTargetId(TenantId tenantId, NotificationRequestStatus status, NotificationTargetId targetId);
boolean existsByStatusAndTemplateId(TenantId tenantId, NotificationRequestStatus status, NotificationTemplateId templateId); boolean existsByTenantIdAndStatusAndTemplateId(TenantId tenantId, NotificationRequestStatus status, NotificationTemplateId templateId);
int removeAllByCreatedTimeBefore(long ts); int removeAllByCreatedTimeBefore(long ts);

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

@ -33,7 +33,7 @@ public interface NotificationRuleDao extends Dao<NotificationRule> {
PageData<NotificationRuleInfo> findInfosByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); PageData<NotificationRuleInfo> findInfosByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId); boolean existsByTenantIdAndTargetId(TenantId tenantId, NotificationTargetId targetId);
List<NotificationRule> findByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType); List<NotificationRule> findByTenantIdAndTriggerType(TenantId tenantId, NotificationRuleTriggerType triggerType);

43
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCacheKey.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class NotificationRequestCacheKey implements Serializable {
private static final long serialVersionUID = 59871139005482170L;
private EntityId originatorEntityId;
private NotificationRuleId ruleId;
@Override
public String toString() {
return ruleId + "_" + originatorEntityId;
}
}

38
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCacheValue.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import java.io.Serializable;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class NotificationRequestCacheValue implements Serializable {
private static final long serialVersionUID = 950211234585105415L;
private List<NotificationRequest> notificationRequests;
}

32
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestCaffeineCache.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service
public class NotificationRequestCaffeineCache extends CaffeineTbTransactionalCache<NotificationRequestCacheKey, NotificationRequestCacheValue> {
public NotificationRequestCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.NOTIFICATION_REQUESTS_CACHE);
}
}

35
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRequestRedisCache.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.notification.cache;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbFSTRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service
public class NotificationRequestRedisCache extends RedisTbTransactionalCache<NotificationRequestCacheKey, NotificationRequestCacheValue> {
public NotificationRequestRedisCache(CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory, TBRedisCacheConfiguration configuration) {
super(CacheConstants.NOTIFICATION_REQUESTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
}
}

2
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleCaffeineCache.java

@ -22,7 +22,7 @@ import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.CacheConstants;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("notificationRuleCache") @Service
public class NotificationRuleCaffeineCache extends CaffeineTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> { public class NotificationRuleCaffeineCache extends CaffeineTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> {
public NotificationRuleCaffeineCache(CacheManager cacheManager) { public NotificationRuleCaffeineCache(CacheManager cacheManager) {

2
dao/src/main/java/org/thingsboard/server/dao/notification/cache/NotificationRuleRedisCache.java

@ -25,7 +25,7 @@ import org.thingsboard.server.cache.TbFSTRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.CacheConstants;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("notificationRuleCache") @Service
public class NotificationRuleRedisCache extends RedisTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> { public class NotificationRuleRedisCache extends RedisTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> {
public NotificationRuleRedisCache(CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory, TBRedisCacheConfiguration configuration) { public NotificationRuleRedisCache(CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory, TBRedisCacheConfiguration configuration) {

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

@ -44,8 +44,6 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.getId;
@Component @Component
@SqlDao @SqlDao
@RequiredArgsConstructor @RequiredArgsConstructor
@ -87,13 +85,13 @@ public class JpaNotificationRequestDao extends JpaAbstractDao<NotificationReques
} }
@Override @Override
public boolean existsByStatusAndTargetId(TenantId tenantId, NotificationRequestStatus status, NotificationTargetId targetId) { public boolean existsByTenantIdAndStatusAndTargetId(TenantId tenantId, NotificationRequestStatus status, NotificationTargetId targetId) {
return notificationRequestRepository.existsByStatusAndTargetsContaining(status, targetId.getId().toString()); return notificationRequestRepository.existsByTenantIdAndStatusAndTargetsContaining(tenantId.getId(), status, targetId.getId().toString());
} }
@Override @Override
public boolean existsByStatusAndTemplateId(TenantId tenantId, NotificationRequestStatus status, NotificationTemplateId templateId) { public boolean existsByTenantIdAndStatusAndTemplateId(TenantId tenantId, NotificationRequestStatus status, NotificationTemplateId templateId) {
return notificationRequestRepository.existsByStatusAndTemplateId(status, templateId.getId()); return notificationRequestRepository.existsByTenantIdAndStatusAndTemplateId(tenantId.getId(), status, templateId.getId());
} }
@Override @Override

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

@ -38,8 +38,6 @@ import org.thingsboard.server.dao.util.SqlDao;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.DaoUtil.getId;
@Component @Component
@SqlDao @SqlDao
@RequiredArgsConstructor @RequiredArgsConstructor
@ -60,8 +58,8 @@ public class JpaNotificationRuleDao extends JpaAbstractDao<NotificationRuleEntit
} }
@Override @Override
public boolean existsByTargetId(TenantId tenantId, NotificationTargetId targetId) { public boolean existsByTenantIdAndTargetId(TenantId tenantId, NotificationTargetId targetId) {
return notificationRuleRepository.existsByRecipientsConfigContaining(targetId.getId().toString()); return notificationRuleRepository.existsByTenantIdAndRecipientsConfigContaining(tenantId.getId(), targetId.getId().toString());
} }
@Override @Override

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

@ -65,9 +65,9 @@ public interface NotificationRequestRepository extends JpaRepository<Notificatio
@Param("status") NotificationRequestStatus status, @Param("status") NotificationRequestStatus status,
@Param("stats") JsonNode stats); @Param("stats") JsonNode stats);
boolean existsByStatusAndTargetsContaining(NotificationRequestStatus status, String targetIdStr); boolean existsByTenantIdAndStatusAndTargetsContaining(UUID tenantId, NotificationRequestStatus status, String targetIdStr);
boolean existsByStatusAndTemplateId(NotificationRequestStatus status, UUID templateId); boolean existsByTenantIdAndStatusAndTemplateId(UUID tenantId, NotificationRequestStatus status, UUID templateId);
@Transactional @Transactional
int deleteAllByCreatedTimeBefore(long ts); int deleteAllByCreatedTimeBefore(long ts);

2
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java

@ -42,7 +42,7 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
boolean existsByRecipientsConfigContaining(String string); boolean existsByTenantIdAndRecipientsConfigContaining(UUID tenantId, String searchString);
List<NotificationRuleEntity> findAllByTenantIdAndTriggerType(UUID tenantId, NotificationRuleTriggerType triggerType); List<NotificationRuleEntity> findAllByTenantIdAndTriggerType(UUID tenantId, NotificationRuleTriggerType triggerType);

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

@ -35,8 +35,8 @@ import java.util.concurrent.ExecutionException;
type = ComponentType.EXTERNAL, type = ComponentType.EXTERNAL,
name = "send notification", name = "send notification",
configClazz = TbNotificationNodeConfiguration.class, configClazz = TbNotificationNodeConfiguration.class,
nodeDescription = "Sends notification to a target", nodeDescription = "Sends notification to targets using the template",
nodeDetails = "Will send notification to the specified target", nodeDetails = "Will send notification to the specified targets",
uiResources = {"static/rulenode/rulenode-core-config.js"} uiResources = {"static/rulenode/rulenode-core-config.js"}
) )
public class TbNotificationNode implements TbNode { public class TbNotificationNode implements TbNode {

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

@ -58,6 +58,7 @@ public class TbSlackNode implements TbNode {
if (token == null) { if (token == null) {
throw new IllegalArgumentException("Slack token is missing"); throw new IllegalArgumentException("Slack token is missing");
} }
String message = TbNodeUtils.processPattern(config.getMessageTemplate(), msg); String message = TbNodeUtils.processPattern(config.getMessageTemplate(), msg);
ListenableFuture<?> result; ListenableFuture<?> result;

1
ui-ngx/src/app/shared/decorators/coerce-boolean.ts

@ -13,6 +13,7 @@
/// See the License for the specific language governing permissions and /// See the License for the specific language governing permissions and
/// limitations under the License. /// limitations under the License.
/// ///
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
export function coerceBoolean() { export function coerceBoolean() {

Loading…
Cancel
Save