Merge pull request #9724 from thingsboard/feature/notifications-widget
Notifications widget
This commit is contained in:
commit
94b670d72e
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -32,4 +32,5 @@ public enum NotificationType {
|
|||||||
EDGE_CONNECTION,
|
EDGE_CONNECTION,
|
||||||
EDGE_COMMUNICATION_FAILURE,
|
EDGE_COMMUNICATION_FAILURE,
|
||||||
TASK_PROCESSING_FAILURE
|
TASK_PROCESSING_FAILURE
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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)));
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user