Merge pull request #9724 from thingsboard/feature/notifications-widget

Notifications widget
This commit is contained in:
Viacheslav Klimov 2024-07-09 11:40:25 +03:00 committed by GitHub
commit 94b670d72e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 180 additions and 17 deletions

View File

@ -350,6 +350,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
NotificationUpdate update = NotificationUpdate.builder() NotificationUpdate update = NotificationUpdate.builder()
.updated(true) .updated(true)
.notificationId(notificationId.getId()) .notificationId(notificationId.getId())
.notificationType(notification.getType())
.newStatus(NotificationStatus.READ) .newStatus(NotificationStatus.READ)
.build(); .build();
onNotificationUpdate(tenantId, recipientId, update); onNotificationUpdate(tenantId, recipientId, update);

View File

@ -21,7 +21,6 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@Data @Data
@ -52,4 +51,5 @@ public abstract class TbSubscription<T> {
public int hashCode() { public int hashCode() {
return Objects.hash(sessionId, subscriptionId, tenantId, entityId, type); return Objects.hash(sessionId, subscriptionId, tenantId, entityId, type);
} }
} }

View File

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus; 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.PageData;
import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
@ -78,6 +79,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
.entityId(securityCtx.getId()) .entityId(securityCtx.getId())
.updateProcessor(this::handleNotificationsSubscriptionUpdate) .updateProcessor(this::handleNotificationsSubscriptionUpdate)
.limit(cmd.getLimit()) .limit(cmd.getLimit())
.notificationTypes(cmd.getTypes())
.build(); .build();
localSubscriptionService.addSubscription(subscription); localSubscriptionService.addSubscription(subscription);
@ -105,8 +107,8 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void fetchUnreadNotifications(NotificationsSubscription subscription) { private void fetchUnreadNotifications(NotificationsSubscription subscription) {
log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId()); log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId());
PageData<Notification> notifications = notificationService.findLatestUnreadNotificationsByRecipientId(subscription.getTenantId(), PageData<Notification> notifications = notificationService.findLatestUnreadNotificationsByRecipientIdAndNotificationTypes(subscription.getTenantId(),
WEB, (UserId) subscription.getEntityId(), subscription.getLimit()); WEB, (UserId) subscription.getEntityId(), subscription.getNotificationTypes(), subscription.getLimit());
subscription.getLatestUnreadNotifications().clear(); subscription.getLatestUnreadNotifications().clear();
notifications.getData().forEach(notification -> { notifications.getData().forEach(notification -> {
subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification); subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification);
@ -139,6 +141,11 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update);
Notification notification = update.getNotification(); Notification notification = update.getNotification();
UUID notificationId = notification != null ? notification.getUuidId() : update.getNotificationId(); 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()) { if (update.isCreated()) {
subscription.getLatestUnreadNotifications().put(notificationId, notification); subscription.getLatestUnreadNotifications().put(notificationId, notification);
subscription.getTotalUnreadCounter().incrementAndGet(); subscription.getTotalUnreadCounter().incrementAndGet();

View File

@ -18,15 +18,19 @@ package org.thingsboard.server.service.ws.notification.cmd;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.service.ws.WsCmd; import org.thingsboard.server.service.ws.WsCmd;
import org.thingsboard.server.service.ws.WsCmdType; import org.thingsboard.server.service.ws.WsCmdType;
import java.util.Set;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class NotificationsSubCmd implements WsCmd { public class NotificationsSubCmd implements WsCmd {
private int cmdId; private int cmdId;
private int limit; private int limit;
private Set<NotificationType> types;
@Override @Override
public WsCmdType getType() { public WsCmdType getType() {

View File

@ -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.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType; import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdateType;
import java.util.Collection; import java.util.List;
@Getter @Getter
@ToString(exclude = "notifications") @ToString(exclude = "notifications")
public class UnreadNotificationsUpdate extends CmdUpdate { public class UnreadNotificationsUpdate extends CmdUpdate {
private final Collection<Notification> notifications; private final List<Notification> notifications;
private final Notification update; private final Notification update;
private final int totalUnreadCount; private final int totalUnreadCount;
private final int sequenceNumber; private final int sequenceNumber;
@ -39,7 +39,7 @@ public class UnreadNotificationsUpdate extends CmdUpdate {
@JsonCreator @JsonCreator
public UnreadNotificationsUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode, public UnreadNotificationsUpdate(@JsonProperty("cmdId") int cmdId, @JsonProperty("errorCode") int errorCode,
@JsonProperty("errorMsg") String errorMsg, @JsonProperty("errorMsg") String errorMsg,
@JsonProperty("notifications") Collection<Notification> notifications, @JsonProperty("notifications") List<Notification> notifications,
@JsonProperty("update") Notification update, @JsonProperty("update") Notification update,
@JsonProperty("totalUnreadCount") int totalUnreadCount, @JsonProperty("totalUnreadCount") int totalUnreadCount,
@JsonProperty("sequenceNumber") int sequenceNumber) { @JsonProperty("sequenceNumber") int sequenceNumber) {

View File

@ -21,6 +21,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.NotificationType;
import java.util.UUID; import java.util.UUID;
@ -31,6 +32,7 @@ import java.util.UUID;
public class NotificationUpdate { public class NotificationUpdate {
private UUID notificationId; private UUID notificationId;
private NotificationType notificationType;
private boolean created; private boolean created;
private Notification notification; private Notification notification;

View File

@ -23,7 +23,6 @@ import org.thingsboard.server.service.subscription.TbSubscription;
import org.thingsboard.server.service.subscription.TbSubscriptionType; import org.thingsboard.server.service.subscription.TbSubscriptionType;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsCountUpdate;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@Getter @Getter

View File

@ -17,10 +17,12 @@ package org.thingsboard.server.service.ws.notification.sub;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.service.subscription.TbSubscription; import org.thingsboard.server.service.subscription.TbSubscription;
import org.thingsboard.server.service.subscription.TbSubscriptionType; import org.thingsboard.server.service.subscription.TbSubscriptionType;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate; import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate;
@ -29,8 +31,8 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -39,13 +41,19 @@ public class NotificationsSubscription extends AbstractNotificationSubscription<
private final Map<UUID, Notification> latestUnreadNotifications = new HashMap<>(); private final Map<UUID, Notification> latestUnreadNotifications = new HashMap<>();
private final int limit; private final int limit;
private final Set<NotificationType> notificationTypes;
@Builder @Builder
public NotificationsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, public NotificationsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId,
BiConsumer<TbSubscription<NotificationsSubscriptionUpdate>, NotificationsSubscriptionUpdate> updateProcessor, BiConsumer<TbSubscription<NotificationsSubscriptionUpdate>, NotificationsSubscriptionUpdate> updateProcessor,
int limit) { int limit, Set<NotificationType> notificationTypes) {
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS, updateProcessor); super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.NOTIFICATIONS, updateProcessor);
this.limit = limit; this.limit = limit;
this.notificationTypes = notificationTypes;
}
public boolean checkNotificationType(NotificationType type) {
return CollectionUtils.isEmpty(notificationTypes) || notificationTypes.contains(type);
} }
public UnreadNotificationsUpdate createFullUpdate() { public UnreadNotificationsUpdate createFullUpdate() {

View File

@ -143,6 +143,14 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
return submitNotificationRequest(targetId, text, 0, deliveryMethods); 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) { protected NotificationRequest submitNotificationRequest(NotificationTargetId targetId, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) {
return submitNotificationRequest(List.of(targetId), text, delayInSec, deliveryMethods); return submitNotificationRequest(List.of(targetId), text, delayInSec, deliveryMethods);
} }

View File

@ -210,6 +210,88 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
checkPartialNotificationsUpdate(otherWsClient.getLastDataUpdate(), notificationText, 1); 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 @Test
public void testMarkingAsRead_multipleSessions() throws Exception { public void testMarkingAsRead_multipleSessions() throws Exception {
connectOtherWsClient(); connectOtherWsClient();

View File

@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.RandomUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.notification.Notification; 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.controller.TbTestWebSocketClient;
import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd;
import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd;
@ -35,7 +36,10 @@ import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Getter @Getter
@ -48,18 +52,28 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
private int unreadCount; private int unreadCount;
private List<Notification> notifications; private List<Notification> notifications;
private final Map<Integer, UnreadNotificationsUpdate> lastUpdates = new ConcurrentHashMap<>();
public NotificationApiWsClient(String wsUrl) throws URISyntaxException { public NotificationApiWsClient(String wsUrl) throws URISyntaxException {
super(new URI(wsUrl + "/api/ws")); super(new URI(wsUrl + "/api/ws"));
} }
public NotificationApiWsClient subscribeForUnreadNotifications(int limit) { public NotificationApiWsClient subscribeForUnreadNotifications(int limit, NotificationType... types) {
send(new NotificationsSubCmd(1, limit)); send(new NotificationsSubCmd(newCmdId(), limit, Arrays.stream(types).collect(Collectors.toSet())));
this.limit = limit; this.limit = limit;
return this; return this;
} }
public int subscribeForUnreadNotificationsAndWait(int limit, NotificationType... types) {
int subId = newCmdId();
send(new NotificationsSubCmd(subId, limit, Arrays.stream(types).collect(Collectors.toSet())));
waitForReply();
this.limit = limit;
return subId;
}
public NotificationApiWsClient subscribeForUnreadNotificationsCount() { public NotificationApiWsClient subscribeForUnreadNotificationsCount() {
send(new NotificationsCountSubCmd(2)); send(new NotificationsCountSubCmd(newCmdId()));
return this; return this;
} }
@ -84,6 +98,7 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
CmdUpdateType updateType = CmdUpdateType.valueOf(update.get("cmdUpdateType").asText()); CmdUpdateType updateType = CmdUpdateType.valueOf(update.get("cmdUpdateType").asText());
if (updateType == CmdUpdateType.NOTIFICATIONS) { if (updateType == CmdUpdateType.NOTIFICATIONS) {
lastDataUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsUpdate.class); lastDataUpdate = JacksonUtil.treeToValue(update, UnreadNotificationsUpdate.class);
lastUpdates.put(lastDataUpdate.getCmdId(), lastDataUpdate);
unreadCount = lastDataUpdate.getTotalUnreadCount(); unreadCount = lastDataUpdate.getTotalUnreadCount();
if (lastDataUpdate.getNotifications() != null) { if (lastDataUpdate.getNotifications() != null) {
notifications = new ArrayList<>(lastDataUpdate.getNotifications()); notifications = new ArrayList<>(lastDataUpdate.getNotifications());
@ -115,7 +130,7 @@ public class NotificationApiWsClient extends TbTestWebSocketClient {
super.onMessage(s); super.onMessage(s);
} }
private static int newCmdId() { private int newCmdId() {
return RandomUtils.nextInt(1, 1000); return RandomUtils.nextInt(1, 1000);
} }

View File

@ -19,10 +19,13 @@ import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
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 java.util.Set;
public interface NotificationService { public interface NotificationService {
Notification saveNotification(TenantId tenantId, Notification notification); Notification saveNotification(TenantId tenantId, Notification notification);
@ -35,7 +38,7 @@ public interface NotificationService {
PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, boolean unreadOnly, PageLink pageLink); PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, boolean unreadOnly, PageLink pageLink);
PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, int limit); PageData<Notification> findLatestUnreadNotificationsByRecipientIdAndNotificationTypes(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, Set<NotificationType> types, int limit);
int countUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId); int countUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);

View File

@ -32,4 +32,5 @@ public enum NotificationType {
EDGE_CONNECTION, EDGE_CONNECTION,
EDGE_COMMUNICATION_FAILURE, EDGE_COMMUNICATION_FAILURE,
TASK_PROCESSING_FAILURE TASK_PROCESSING_FAILURE
} }

View File

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus; 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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.SortOrder;
@ -34,6 +35,7 @@ import org.thingsboard.server.dao.entity.EntityDaoService;
import org.thingsboard.server.dao.sql.query.EntityKeyMapping; import org.thingsboard.server.dao.sql.query.EntityKeyMapping;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
@Service @Service
@Slf4j @Slf4j
@ -72,10 +74,10 @@ public class DefaultNotificationService implements NotificationService, EntityDa
} }
@Override @Override
public PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, int limit) { public PageData<Notification> findLatestUnreadNotificationsByRecipientIdAndNotificationTypes(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, Set<NotificationType> types, int limit) {
SortOrder sortOrder = new SortOrder(EntityKeyMapping.CREATED_TIME, SortOrder.Direction.DESC); SortOrder sortOrder = new SortOrder(EntityKeyMapping.CREATED_TIME, SortOrder.Direction.DESC);
PageLink pageLink = new PageLink(limit, 0, null, sortOrder); PageLink pageLink = new PageLink(limit, 0, null, sortOrder);
return findNotificationsByRecipientIdAndReadStatus(tenantId, deliveryMethod, recipientId, true, pageLink); return notificationDao.findUnreadByDeliveryMethodAndRecipientIdAndNotificationTypesAndPageLink(tenantId, deliveryMethod, recipientId, types, pageLink);
} }
@Override @Override

View File

@ -22,14 +22,19 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus; 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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import java.util.Set;
public interface NotificationDao extends Dao<Notification> { public interface NotificationDao extends Dao<Notification> {
PageData<Notification> findUnreadByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink); PageData<Notification> findUnreadByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink);
PageData<Notification> findUnreadByDeliveryMethodAndRecipientIdAndNotificationTypesAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, Set<NotificationType> types, PageLink pageLink);
PageData<Notification> findByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink); PageData<Notification> findByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink);
boolean updateStatusByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId, NotificationStatus status); boolean updateStatusByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId, NotificationStatus status);

View File

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.sql.notification; package org.thingsboard.server.dao.sql.notification;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus; 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.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
@ -37,6 +39,7 @@ import org.thingsboard.server.dao.sql.JpaPartitionedAbstractDao;
import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -58,6 +61,14 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao<NotificationEn
} }
@Override @Override
public PageData<Notification> findUnreadByDeliveryMethodAndRecipientIdAndNotificationTypesAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, Set<NotificationType> types, PageLink pageLink) {
if (CollectionUtils.isEmpty(types)) {
return findUnreadByDeliveryMethodAndRecipientIdAndPageLink(tenantId, deliveryMethod, recipientId, pageLink);
}
return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientIdAndTypeInAndStatusNot(deliveryMethod,
recipientId.getId(), types, NotificationStatus.READ, pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
}
public PageData<Notification> findByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink) { public PageData<Notification> findByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientId(deliveryMethod, recipientId.getId(), return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientId(deliveryMethod, recipientId.getId(),
pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));

View File

@ -25,8 +25,10 @@ import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus; 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 org.thingsboard.server.dao.model.sql.NotificationEntity;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
@Repository @Repository
@ -42,7 +44,20 @@ public interface NotificationRepository extends JpaRepository<NotificationEntity
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
@Query("SELECT n FROM NotificationEntity n WHERE n.deliveryMethod = :deliveryMethod AND n.recipientId = :recipientId " + @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<NotificationEntity> findByDeliveryMethodAndRecipientIdAndTypeInAndStatusNot(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod,
@Param("recipientId") UUID recipientId,
@Param("types") Set<NotificationType> types,
@Param("status") NotificationStatus status,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT n FROM NotificationEntity n WHERE n.deliveryMethod = :deliveryMethod " +
"AND n.recipientId = :recipientId " +
"AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " + "AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " +
"OR ilike(n.text, concat('%', :searchText, '%')) = true)") "OR ilike(n.text, concat('%', :searchText, '%')) = true)")
Page<NotificationEntity> findByDeliveryMethodAndRecipientId(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod, Page<NotificationEntity> findByDeliveryMethodAndRecipientId(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod,