From 30b0aea2623fcae65ccdbbaa644efe75e192f9ed Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 29 Nov 2023 13:01:51 +0200 Subject: [PATCH 1/4] Notification types filter for unread notifications subscription --- .../DefaultNotificationCenter.java | 5 ++ .../DefaultNotificationCommandsHandler.java | 12 ++- .../notification/cmd/NotificationsSubCmd.java | 4 + .../cmd/UnreadNotificationsUpdate.java | 6 +- .../notification/sub/NotificationUpdate.java | 2 + .../sub/NotificationsSubscription.java | 10 ++- .../AbstractNotificationApiTest.java | 8 ++ .../notification/NotificationApiTest.java | 82 +++++++++++++++++++ .../notification/NotificationApiWsClient.java | 25 +++++- .../dao/notification/NotificationService.java | 5 +- .../data/notification/NotificationType.java | 7 +- .../DefaultNotificationService.java | 6 +- .../dao/notification/NotificationDao.java | 5 ++ .../sql/notification/JpaNotificationDao.java | 13 ++- .../notification/NotificationRepository.java | 12 +++ 15 files changed, 187 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index b4ba871edf..308f7908cd 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -329,12 +329,17 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple @Override public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { + Notification notification = notificationService.findNotificationById(tenantId, notificationId); + if (notification == null) { + return; + } boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); if (updated) { log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId); NotificationUpdate update = NotificationUpdate.builder() .updated(true) .notificationId(notificationId.getId()) + .notificationType(notification.getType()) .newStatus(NotificationStatus.READ) .build(); onNotificationUpdate(tenantId, recipientId, update); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 0937275a0d..5490148b74 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.ws.notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; @@ -76,6 +78,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH .entityId(securityCtx.getId()) .updateProcessor(this::handleNotificationsSubscriptionUpdate) .limit(cmd.getLimit()) + .notificationTypes(CollectionUtils.isNotEmpty(cmd.getTypes()) ? cmd.getTypes() : NotificationType.all) .build(); localSubscriptionService.addSubscription(subscription); @@ -103,8 +106,8 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void fetchUnreadNotifications(NotificationsSubscription subscription) { log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId()); - PageData notifications = notificationService.findLatestUnreadNotificationsByRecipientId(subscription.getTenantId(), - (UserId) subscription.getEntityId(), subscription.getLimit()); + PageData notifications = notificationService.findLatestUnreadNotificationsByRecipientIdAndNotificationTypes(subscription.getTenantId(), + (UserId) subscription.getEntityId(), subscription.getNotificationTypes(), subscription.getLimit()); subscription.getLatestUnreadNotifications().clear(); notifications.getData().forEach(notification -> { subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification); @@ -137,6 +140,11 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); Notification notification = update.getNotification(); UUID notificationId = notification != null ? notification.getUuidId() : update.getNotificationId(); + NotificationType notificationType = notification != null ? notification.getType() : update.getNotificationType(); + if (notificationType != null && !subscription.checkNotificationType(notificationType)) { + return; + } + if (update.isCreated()) { subscription.getLatestUnreadNotifications().put(notificationId, notification); subscription.getTotalUnreadCounter().incrementAndGet(); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java index e022d2c49f..f827f46209 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationsSubCmd.java @@ -18,6 +18,9 @@ package org.thingsboard.server.service.ws.notification.cmd; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.notification.NotificationType; + +import java.util.Set; @Data @NoArgsConstructor @@ -25,4 +28,5 @@ import lombok.NoArgsConstructor; public class NotificationsSubCmd implements WsCmd { private int cmdId; private int limit; + private Set types; } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java index e64624d16e..c0de2103ed 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java @@ -24,13 +24,13 @@ import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; -import java.util.Collection; +import java.util.List; @Getter @ToString(exclude = "notifications") public class UnreadNotificationsUpdate extends CmdUpdate { - private final Collection notifications; + private final List notifications; private final Notification update; private final int totalUnreadCount; @@ -38,7 +38,7 @@ public class UnreadNotificationsUpdate extends CmdUpdate { @JsonCreator public UnreadNotificationsUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode, @JsonProperty("errorMsg") String errorMsg, - @JsonProperty("notifications") Collection notifications, + @JsonProperty("notifications") List notifications, @JsonProperty("update") Notification update, @JsonProperty("totalUnreadCount") int totalUnreadCount) { super(cmdId, errorCode, errorMsg); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java index b00231a5c9..41191bc8d2 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java @@ -21,6 +21,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.notification.NotificationType; import java.util.UUID; @@ -31,6 +32,7 @@ import java.util.UUID; public class NotificationUpdate { private UUID notificationId; + private NotificationType notificationType; private boolean created; private Notification notification; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java index ede82f9ff7..8751745030 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.service.subscription.TbSubscription; import org.thingsboard.server.service.subscription.TbSubscriptionType; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; @@ -29,6 +30,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; @@ -39,14 +41,20 @@ public class NotificationsSubscription extends TbSubscription latestUnreadNotifications = new HashMap<>(); private final int limit; + private final Set notificationTypes; private final AtomicInteger totalUnreadCounter = new AtomicInteger(); @Builder public NotificationsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, BiConsumer, NotificationsSubscriptionUpdate> updateProcessor, - int limit) { + int limit, Set notificationTypes) { super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS, updateProcessor); this.limit = limit; + this.notificationTypes = notificationTypes; + } + + public boolean checkNotificationType(NotificationType type) { + return notificationTypes.contains(type); } public UnreadNotificationsUpdate createFullUpdate() { diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index 27a0eed1a4..364ef2e18a 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -129,6 +129,14 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest return submitNotificationRequest(targetId, text, 0, deliveryMethods); } + protected NotificationRequest submitNotificationRequest(NotificationType type, NotificationTargetId targetId, String text, NotificationDeliveryMethod... deliveryMethods) { + if (deliveryMethods.length == 0) { + deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEB}; + } + NotificationTemplate notificationTemplate = createNotificationTemplate(type, DEFAULT_NOTIFICATION_SUBJECT, text, deliveryMethods); + return submitNotificationRequest(List.of(targetId), notificationTemplate.getId(), 0); + } + protected NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) { return submitNotificationRequest(List.of(targetId), text, delayInSec, deliveryMethods); } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index ba0b4e4763..f47dae90a8 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -183,6 +183,88 @@ public class NotificationApiTest extends AbstractNotificationApiTest { checkPartialNotificationsUpdate(otherWsClient.getLastDataUpdate(), notificationText, 1); } + @Test + public void testNotificationUpdates_typesFilter_multipleSubs() { + int generalSub = wsClient.subscribeForUnreadNotificationsAndWait(10, NotificationType.GENERAL); + int alarmSub = wsClient.subscribeForUnreadNotificationsAndWait(10, NotificationType.ALARM, NotificationType.GENERAL); + int entityActionSub = wsClient.subscribeForUnreadNotificationsAndWait(10, NotificationType.ENTITY_ACTION, NotificationType.GENERAL); + NotificationTarget notificationTarget = createNotificationTarget(customerUserId); + + String generalNotificationText1 = "General notification 1"; + submitNotificationRequest(NotificationType.GENERAL, notificationTarget.getId(), generalNotificationText1); + // expecting all 3 subs to received update + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, alarmSub, entityActionSub) + .allMatch(update -> update.getUpdate().getText().equals(generalNotificationText1) + && update.getTotalUnreadCount() == 1); + }); + Notification generalNotification1 = wsClient.getLastDataUpdate().getUpdate(); + + String generalNotificationText2 = "General notification 2"; + submitNotificationRequest(NotificationType.GENERAL, notificationTarget.getId(), generalNotificationText2); + // expecting all 3 subs to received update + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, alarmSub, entityActionSub) + .allMatch(update -> update.getUpdate().getText().equals(generalNotificationText2) + && update.getTotalUnreadCount() == 2); + }); + Notification generalNotification2 = wsClient.getLastDataUpdate().getUpdate(); + + // marking as read, expecting all 3 subs to received update + wsClient.markNotificationAsRead(generalNotification1.getUuidId()); + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, alarmSub, entityActionSub) + .allMatch(update -> update.getTotalUnreadCount() == 1 && update.getNotifications().size() == 1 + && update.getNotifications().get(0).getText().equals(generalNotificationText2)); + }); + wsClient.getLastUpdates().clear(); + + String alarmNotificationText1 = "Alarm notification 1"; + submitNotificationRequest(NotificationType.ALARM, notificationTarget.getId(), alarmNotificationText1); + // expecting only 1 sub to received update + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + assertThat(wsClient.getLastUpdates()).extractingByKey(alarmSub) + .matches(update -> update.getUpdate().getText().equals(alarmNotificationText1) + && update.getTotalUnreadCount() == 2); + }); + Notification alarmNotification1 = wsClient.getLastDataUpdate().getUpdate(); + + String alarmNotificationText2 = "Alarm notification 2"; + submitNotificationRequest(NotificationType.ALARM, notificationTarget.getId(), alarmNotificationText2); + // expecting only 1 sub to received update + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + assertThat(wsClient.getLastUpdates()).extractingByKey(alarmSub) + .matches(update -> update.getUpdate().getText().equals(alarmNotificationText2) + && update.getTotalUnreadCount() == 3); + }); + await().during(3, TimeUnit.SECONDS) + .untilAsserted(() -> { + assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, entityActionSub) + .containsOnlyNulls(); + }); + + // marking as read, expecting only 1 sub to receive update + wsClient.markNotificationAsRead(alarmNotification1.getUuidId()); + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + assertThat(wsClient.getLastUpdates()).extractingByKey(alarmSub) + .matches(update -> update.getTotalUnreadCount() == 2 && update.getNotifications().size() == 2); + }); + await().during(3, TimeUnit.SECONDS).untilAsserted(() -> { + assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, entityActionSub) + .containsOnlyNulls(); + }); + + // marking as read, expecting general and entity action subs with 0 unread, and alarm with 1 unread + wsClient.markNotificationAsRead(generalNotification2.getUuidId()); + await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + assertThat(wsClient.getLastUpdates()).extractingByKeys(generalSub, entityActionSub) + .allMatch(update -> update.getTotalUnreadCount() == 0 && update.getNotifications().isEmpty()); + assertThat(wsClient.getLastUpdates()).extractingByKey(alarmSub) + .matches(update -> update.getTotalUnreadCount() == 1 && update.getNotifications().size() == 1 + && update.getNotifications().get(0).getText().equals(alarmNotificationText2)); + }); + } + @Test public void testMarkingAsRead_multipleSessions() throws Exception { connectOtherWsClient(); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java index 31023fe258..30d3ceef5e 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; @@ -36,7 +37,10 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; @Slf4j @Getter @@ -49,21 +53,33 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { private int unreadCount; private List notifications; + private final Map lastUpdates = new ConcurrentHashMap<>(); + public NotificationApiWsClient(String wsUrl, String token) throws URISyntaxException { super(new URI(wsUrl + "/api/ws/plugins/telemetry?token=" + token)); } - public NotificationApiWsClient subscribeForUnreadNotifications(int limit) { + public NotificationApiWsClient subscribeForUnreadNotifications(int limit, NotificationType... types) { WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); - cmdsWrapper.setUnreadNotificationsSubCmd(new NotificationsSubCmd(1, limit)); + cmdsWrapper.setUnreadNotificationsSubCmd(new NotificationsSubCmd(newCmdId(), limit, Arrays.stream(types).collect(Collectors.toSet()))); sendCmd(cmdsWrapper); this.limit = limit; return this; } + public int subscribeForUnreadNotificationsAndWait(int limit, NotificationType... types) { + WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); + int subId = newCmdId(); + cmdsWrapper.setUnreadNotificationsSubCmd(new NotificationsSubCmd(subId, limit, Arrays.stream(types).collect(Collectors.toSet()))); + sendCmd(cmdsWrapper); + waitForReply(); + this.limit = limit; + return subId; + } + public NotificationApiWsClient subscribeForUnreadNotificationsCount() { WsCmdsWrapper cmdsWrapper = new WsCmdsWrapper(); - cmdsWrapper.setUnreadNotificationsCountSubCmd(new NotificationsCountSubCmd(2)); + cmdsWrapper.setUnreadNotificationsCountSubCmd(new NotificationsCountSubCmd(newCmdId())); sendCmd(cmdsWrapper); return this; } @@ -98,6 +114,7 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { CmdUpdateType updateType = CmdUpdateType.valueOf(update.get("cmdUpdateType").asText()); if (updateType == CmdUpdateType.NOTIFICATIONS) { lastDataUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsUpdate.class); + lastUpdates.put(lastDataUpdate.getCmdId(), lastDataUpdate); unreadCount = lastDataUpdate.getTotalUnreadCount(); if (lastDataUpdate.getNotifications() != null) { notifications = new ArrayList<>(lastDataUpdate.getNotifications()); @@ -126,7 +143,7 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { super.onMessage(s); } - private static int newCmdId() { + private int newCmdId() { return RandomUtils.nextInt(1, 1000); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index e747d6ba30..34668af158 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -19,9 +19,12 @@ import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import java.util.Set; + public interface NotificationService { Notification saveNotification(TenantId tenantId, Notification notification); @@ -34,7 +37,7 @@ public interface NotificationService { PageData findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, UserId recipientId, boolean unreadOnly, PageLink pageLink); - PageData findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit); + PageData findLatestUnreadNotificationsByRecipientIdAndNotificationTypes(TenantId tenantId, UserId recipientId, Set types, int limit); int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java index 251fae2ac0..9e95ce3cf2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java @@ -15,6 +15,9 @@ */ package org.thingsboard.server.common.data.notification; +import java.util.EnumSet; +import java.util.Set; + public enum NotificationType { GENERAL, @@ -28,6 +31,8 @@ public enum NotificationType { ENTITIES_LIMIT, API_USAGE_LIMIT, RULE_NODE, - RATE_LIMITS + RATE_LIMITS; + + public static final Set all = EnumSet.allOf(NotificationType.class); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index 20a32efbe0..93e5a051dc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; @@ -33,6 +34,7 @@ import org.thingsboard.server.dao.entity.EntityDaoService; import org.thingsboard.server.dao.sql.query.EntityKeyMapping; import java.util.Optional; +import java.util.Set; @Service @Slf4j @@ -71,10 +73,10 @@ public class DefaultNotificationService implements NotificationService, EntityDa } @Override - public PageData findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit) { + public PageData findLatestUnreadNotificationsByRecipientIdAndNotificationTypes(TenantId tenantId, UserId recipientId, Set types, int limit) { SortOrder sortOrder = new SortOrder(EntityKeyMapping.CREATED_TIME, SortOrder.Direction.DESC); PageLink pageLink = new PageLink(limit, 0, null, sortOrder); - return findNotificationsByRecipientIdAndReadStatus(tenantId, recipientId, true, pageLink); + return notificationDao.findUnreadByRecipientIdAndNotificationTypesAndPageLink(tenantId, recipientId, types, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java index fb3890fcbb..ad97bcc3ba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java @@ -21,14 +21,19 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import java.util.Set; + public interface NotificationDao extends Dao { PageData findUnreadByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink); + PageData findUnreadByRecipientIdAndNotificationTypesAndPageLink(TenantId tenantId, UserId recipientId, Set types, PageLink pageLink); + PageData findByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink); boolean updateStatusByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId, NotificationStatus status); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index 57a8306fc1..f0adc10223 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -16,8 +16,8 @@ package org.thingsboard.server.dao.sql.notification; import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; +import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; @@ -38,6 +39,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.dao.util.SqlDao; +import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -70,6 +72,15 @@ public class JpaNotificationDao extends JpaAbstractDao findUnreadByRecipientIdAndNotificationTypesAndPageLink(TenantId tenantId, UserId recipientId, Set types, PageLink pageLink) { + if (CollectionUtils.isEmpty(types)) { + types = NotificationType.all; + } + return DaoUtil.toPageData(notificationRepository.findByRecipientIdAndTypeInAndStatusNot(recipientId.getId(), types, NotificationStatus.READ, + pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); + } + @Override public PageData findByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink) { return DaoUtil.toPageData(notificationRepository.findByRecipientId(recipientId.getId(), diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index 6b986716bb..6ec81effe7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -24,8 +24,10 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.dao.model.sql.NotificationEntity; +import java.util.Set; import java.util.UUID; @Repository @@ -39,6 +41,16 @@ public interface NotificationRepository extends JpaRepository :status AND (n.type IN :types) " + + "AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " + + "OR ilike(n.text, concat('%', :searchText, '%')) = true)") + Page findByRecipientIdAndTypeInAndStatusNot(@Param("recipientId") UUID recipientId, + @Param("types") Set types, + @Param("status") NotificationStatus status, + @Param("searchText") String searchText, + Pageable pageable); + @Query("SELECT n FROM NotificationEntity n WHERE n.recipientId = :recipientId " + "AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " + "OR ilike(n.text, concat('%', :searchText, '%')) = true)") From 9d8b6f2195eec054ad00619e420d194bfbf88378 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Wed, 6 Dec 2023 17:21:44 +0200 Subject: [PATCH 2/4] Add sequenceNumber to notification subscription updates --- .../server/service/subscription/TbSubscription.java | 3 +++ .../ws/notification/cmd/UnreadNotificationsCountUpdate.java | 5 ++++- .../ws/notification/cmd/UnreadNotificationsUpdate.java | 5 ++++- .../ws/notification/sub/NotificationsCountSubscription.java | 1 + .../ws/notification/sub/NotificationsSubscription.java | 3 +++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java index 1cda590a1f..04007e949c 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; @Data @@ -35,6 +36,8 @@ public abstract class TbSubscription { private final TbSubscriptionType type; private final BiConsumer, T> updateProcessor; + protected final AtomicInteger sequence = new AtomicInteger(); + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java index 93f51a965b..42dc670a21 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsCountUpdate.java @@ -28,14 +28,17 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; public class UnreadNotificationsCountUpdate extends CmdUpdate { private final int totalUnreadCount; + private final int sequenceNumber; @Builder @JsonCreator public UnreadNotificationsCountUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode, @JsonProperty("errorMsg") String errorMsg, - @JsonProperty("totalUnreadCount") int totalUnreadCount) { + @JsonProperty("totalUnreadCount") int totalUnreadCount, + @JsonProperty("sequenceNumber") int sequenceNumber) { super(cmdId, errorCode, errorMsg); this.totalUnreadCount = totalUnreadCount; + this.sequenceNumber = sequenceNumber; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java index c0de2103ed..bb3ac6e77e 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/UnreadNotificationsUpdate.java @@ -33,6 +33,7 @@ public class UnreadNotificationsUpdate extends CmdUpdate { private final List notifications; private final Notification update; private final int totalUnreadCount; + private final int sequenceNumber; @Builder @JsonCreator @@ -40,11 +41,13 @@ public class UnreadNotificationsUpdate extends CmdUpdate { @JsonProperty("errorMsg") String errorMsg, @JsonProperty("notifications") List notifications, @JsonProperty("update") Notification update, - @JsonProperty("totalUnreadCount") int totalUnreadCount) { + @JsonProperty("totalUnreadCount") int totalUnreadCount, + @JsonProperty("sequenceNumber") int sequenceNumber) { super(cmdId, errorCode, errorMsg); this.notifications = notifications; this.update = update; this.totalUnreadCount = totalUnreadCount; + this.sequenceNumber = sequenceNumber; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java index 5263f9d651..94939682e9 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsCountSubscription.java @@ -41,6 +41,7 @@ public class NotificationsCountSubscription extends TbSubscription Date: Thu, 20 Jun 2024 11:20:54 +0300 Subject: [PATCH 3/4] Fix unread notifications query --- .../notification/DefaultNotificationCommandsHandler.java | 2 +- .../dao/sql/notification/NotificationRepository.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 2e8a1e9ab0..8e3c1dd457 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -17,7 +17,7 @@ package org.thingsboard.server.service.ws.notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index 5deac62b7d..38bcff442c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -44,8 +44,9 @@ public interface NotificationRepository extends JpaRepository :status AND (n.type IN :types) " + + @Query("SELECT n FROM NotificationEntity n WHERE n.deliveryMethod = :deliveryMethod " + + "AND n.recipientId = :recipientId AND n.status <> :status " + + "AND (n.type IN :types) " + "AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " + "OR ilike(n.text, concat('%', :searchText, '%')) = true)") Page findByDeliveryMethodAndRecipientIdAndTypeInAndStatusNot(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod, @@ -55,7 +56,8 @@ public interface NotificationRepository extends JpaRepository findByDeliveryMethodAndRecipientId(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod, From d9e90ea663f3641cc839ce03c3f6d425f3394a89 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 20 Jun 2024 11:33:28 +0300 Subject: [PATCH 4/4] Refactoring for notification types filter --- .../server/service/subscription/TbSubscription.java | 4 +--- .../notification/DefaultNotificationCommandsHandler.java | 3 +-- .../ws/notification/sub/NotificationsSubscription.java | 3 ++- .../server/common/data/notification/NotificationType.java | 7 +------ .../server/dao/sql/notification/JpaNotificationDao.java | 2 +- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java index 256a8ac89c..4324a3514a 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscription.java @@ -21,7 +21,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; @Data @@ -36,8 +35,6 @@ public abstract class TbSubscription { private final TbSubscriptionType type; private final BiConsumer, T> updateProcessor; - protected final AtomicInteger sequence = new AtomicInteger(); - @Override public boolean equals(Object o) { if (this == o) return true; @@ -54,4 +51,5 @@ public abstract class TbSubscription { public int hashCode() { return Objects.hash(sessionId, subscriptionId, tenantId, entityId, type); } + } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 8e3c1dd457..b8c83fe286 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -17,7 +17,6 @@ package org.thingsboard.server.service.ws.notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -80,7 +79,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH .entityId(securityCtx.getId()) .updateProcessor(this::handleNotificationsSubscriptionUpdate) .limit(cmd.getLimit()) - .notificationTypes(CollectionUtils.isNotEmpty(cmd.getTypes()) ? cmd.getTypes() : NotificationType.all) + .notificationTypes(cmd.getTypes()) .build(); localSubscriptionService.addSubscription(subscription); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java index 5b2fc8d125..58ced7c4bf 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationsSubscription.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.ws.notification.sub; import lombok.Builder; import lombok.Getter; +import org.apache.commons.collections4.CollectionUtils; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -52,7 +53,7 @@ public class NotificationsSubscription extends AbstractNotificationSubscription< } public boolean checkNotificationType(NotificationType type) { - return notificationTypes.contains(type); + return CollectionUtils.isEmpty(notificationTypes) || notificationTypes.contains(type); } public UnreadNotificationsUpdate createFullUpdate() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java index 59e43fbd4e..ec66a3d53a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java @@ -15,9 +15,6 @@ */ package org.thingsboard.server.common.data.notification; -import java.util.EnumSet; -import java.util.Set; - public enum NotificationType { GENERAL, @@ -34,8 +31,6 @@ public enum NotificationType { RATE_LIMITS, EDGE_CONNECTION, EDGE_COMMUNICATION_FAILURE, - TASK_PROCESSING_FAILURE; - - public static final Set all = EnumSet.allOf(NotificationType.class); + TASK_PROCESSING_FAILURE } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index fe14d602a6..22d2747e3b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -63,7 +63,7 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao findUnreadByDeliveryMethodAndRecipientIdAndNotificationTypesAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, Set types, PageLink pageLink) { if (CollectionUtils.isEmpty(types)) { - types = NotificationType.all; + return findUnreadByDeliveryMethodAndRecipientIdAndPageLink(tenantId, deliveryMethod, recipientId, pageLink); } return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientIdAndTypeInAndStatusNot(deliveryMethod, recipientId.getId(), types, NotificationStatus.READ, pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));