Browse Source

Add delivery_method column to notification; persist mobile notifications

pull/10420/head
ViacheslavKlimov 2 years ago
parent
commit
ed44db6d25
  1. 27
      application/src/main/data/upgrade/3.6.3/schema_update.sql
  2. 12
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  3. 32
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  4. 30
      application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java
  5. 23
      application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java
  6. 8
      application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
  7. 10
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  8. 77
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  9. 9
      common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java
  10. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java
  11. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  12. 7
      dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java
  13. 19
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java
  14. 11
      dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java
  15. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java
  16. 33
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java
  17. 4
      dao/src/main/resources/sql/schema-entities-idx.sql
  18. 1
      dao/src/main/resources/sql/schema-entities.sql
  19. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java
  20. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java

27
application/src/main/data/upgrade/3.6.3/schema_update.sql

@ -0,0 +1,27 @@
--
-- Copyright © 2016-2024 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- NOTIFICATIONS UPDATE START
ALTER TABLE notification ADD COLUMN IF NOT EXISTS delivery_method VARCHAR(50) NOT NULL default 'WEB';
DROP INDEX IF EXISTS idx_notification_recipient_id_created_time;
DROP INDEX IF EXISTS idx_notification_recipient_id_unread;
CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_created_time ON notification(delivery_method, recipient_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_unread ON notification(delivery_method, recipient_id) WHERE status <> 'READ';
-- NOTIFICATIONS UPDATE END

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

@ -105,6 +105,8 @@ public class NotificationController extends BaseController {
private final NotificationCenter notificationCenter;
private final NotificationSettingsService notificationSettingsService;
private static final String DELIVERY_METHOD_ALLOWABLE_VALUES = "WEB,MOBILE_APP";
@ApiOperation(value = "Get notifications (getNotifications)",
notes = "Returns the page of notifications for current user." + NEW_LINE +
PAGE_DATA_PARAMETERS +
@ -172,10 +174,12 @@ public class NotificationController extends BaseController {
@RequestParam(required = false) String sortOrder,
@ApiParam(value = "To search for unread notifications only")
@RequestParam(defaultValue = "false") boolean unreadOnly,
@ApiParam(value = "Delivery method", allowableValues = DELIVERY_METHOD_ALLOWABLE_VALUES)
@RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod,
@AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
// no permissions
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink);
return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), deliveryMethod, user.getId(), unreadOnly, pageLink);
}
@ApiOperation(value = "Mark notification as read (markNotificationAsRead)",
@ -195,9 +199,11 @@ public class NotificationController extends BaseController {
AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PutMapping("/notifications/read")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
public void markAllNotificationsAsRead(@AuthenticationPrincipal SecurityUser user) {
public void markAllNotificationsAsRead(@ApiParam(value = "Delivery method", allowableValues = DELIVERY_METHOD_ALLOWABLE_VALUES)
@RequestParam(defaultValue = "WEB") NotificationDeliveryMethod deliveryMethod,
@AuthenticationPrincipal SecurityUser user) {
// no permissions
notificationCenter.markAllNotificationsAsRead(user.getTenantId(), user.getId());
notificationCenter.markAllNotificationsAsRead(user.getTenantId(), deliveryMethod, user.getId());
}
@ApiOperation(value = "Delete notification (deleteNotification)",

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

@ -82,6 +82,8 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.WEB;
@Service
@Slf4j
@RequiredArgsConstructor
@ -192,7 +194,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
NotificationProcessingContext ctx = NotificationProcessingContext.builder()
.tenantId(tenantId)
.request(notificationRequest)
.deliveryMethods(Set.of(NotificationDeliveryMethod.WEB))
.deliveryMethods(Set.of(WEB))
.template(template)
.build();
@ -323,6 +325,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
.requestId(request.getId())
.recipientId(recipient.getId())
.type(ctx.getNotificationType())
.deliveryMethod(WEB)
.subject(processedTemplate.getSubject())
.text(processedTemplate.getBody())
.additionalConfig(processedTemplate.getAdditionalConfig())
@ -348,19 +351,22 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
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())
.newStatus(NotificationStatus.READ)
.build();
onNotificationUpdate(tenantId, recipientId, update);
Notification notification = notificationService.findNotificationById(tenantId, notificationId);
if (notification.getDeliveryMethod() == WEB) {
NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
.notificationId(notificationId.getId())
.newStatus(NotificationStatus.READ)
.build();
onNotificationUpdate(tenantId, recipientId, update);
}
}
}
@Override
public void markAllNotificationsAsRead(TenantId tenantId, UserId recipientId) {
int updatedCount = notificationService.markAllNotificationsAsRead(tenantId, recipientId);
if (updatedCount > 0) {
public void markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) {
int updatedCount = notificationService.markAllNotificationsAsRead(tenantId, deliveryMethod, recipientId);
if (updatedCount > 0 && deliveryMethod == WEB) {
log.trace("Marked all notifications as read (recipient id: {}, tenant id: {})", recipientId, tenantId);
NotificationUpdate update = NotificationUpdate.builder()
.updated(true)
@ -375,7 +381,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
public void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId) {
Notification notification = notificationService.findNotificationById(tenantId, notificationId);
boolean deleted = notificationService.deleteNotification(tenantId, recipientId, notificationId);
if (deleted) {
if (deleted && notification.getDeliveryMethod() == WEB) {
NotificationUpdate update = NotificationUpdate.builder()
.deleted(true)
.notification(notification)
@ -455,7 +461,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.WEB;
return WEB;
}
@Override
@ -466,7 +472,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
@Autowired
public void setChannels(List<NotificationChannel> channels, NotificationCenter webNotificationChannel) {
this.channels = channels.stream().collect(Collectors.toMap(NotificationChannel::getDeliveryMethod, c -> c));
this.channels.put(NotificationDeliveryMethod.WEB, (NotificationChannel) webNotificationChannel);
this.channels.put(WEB, (NotificationChannel) webNotificationChannel);
}
}

30
application/src/main/java/org/thingsboard/server/service/notification/channels/MobileAppNotificationChannel.java

@ -26,11 +26,15 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.notification.FirebaseService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationRequest;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
import org.thingsboard.server.common.data.notification.settings.MobileAppNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.template.MobileAppDeliveryMethodNotificationTemplate;
import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.notification.NotificationProcessingContext;
@ -41,6 +45,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.MOBILE_APP;
@Component
@RequiredArgsConstructor
@Slf4j
@ -48,25 +54,41 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
private final FirebaseService firebaseService;
private final UserService userService;
private final NotificationService notificationService;
private final NotificationSettingsService notificationSettingsService;
@Override
public void sendNotification(User recipient, MobileAppDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) throws Exception {
NotificationRequest request = ctx.getRequest();
Notification notification = Notification.builder()
.requestId(request.getId())
.recipientId(recipient.getId())
.type(ctx.getNotificationType())
.deliveryMethod(MOBILE_APP)
.subject(processedTemplate.getSubject())
.text(processedTemplate.getBody())
.additionalConfig(processedTemplate.getAdditionalConfig())
.info(request.getInfo())
.status(NotificationStatus.SENT)
.build();
notificationService.saveNotification(recipient.getTenantId(), notification);
var mobileSessions = userService.findMobileSessions(recipient.getTenantId(), recipient.getId());
if (mobileSessions.isEmpty()) {
throw new IllegalArgumentException("User doesn't use the mobile app");
}
MobileAppNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.MOBILE_APP);
MobileAppNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(MOBILE_APP);
String credentials = config.getFirebaseServiceAccountCredentials();
Set<String> validTokens = new HashSet<>(mobileSessions.keySet());
String subject = processedTemplate.getSubject();
String body = processedTemplate.getBody();
Map<String, String> data = getNotificationData(processedTemplate, ctx);
int unreadCount = notificationService.countUnreadNotificationsByRecipientId(ctx.getTenantId(), MOBILE_APP, recipient.getId());
for (String token : mobileSessions.keySet()) {
try {
firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data);
firebaseService.sendMessage(ctx.getTenantId(), credentials, token, subject, body, data, unreadCount);
} catch (FirebaseMessagingException e) {
MessagingErrorCode errorCode = e.getMessagingErrorCode();
if (errorCode == MessagingErrorCode.UNREGISTERED || errorCode == MessagingErrorCode.INVALID_ARGUMENT) {
@ -116,14 +138,14 @@ public class MobileAppNotificationChannel implements NotificationChannel<User, M
@Override
public void check(TenantId tenantId) throws Exception {
NotificationSettings systemSettings = notificationSettingsService.findNotificationSettings(TenantId.SYS_TENANT_ID);
if (!systemSettings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.MOBILE_APP)) {
if (!systemSettings.getDeliveryMethodsConfigs().containsKey(MOBILE_APP)) {
throw new RuntimeException("Push-notifications to mobile are not configured");
}
}
@Override
public NotificationDeliveryMethod getDeliveryMethod() {
return NotificationDeliveryMethod.MOBILE_APP;
return MOBILE_APP;
}
}

23
application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java

@ -54,7 +54,8 @@ public class DefaultFirebaseService implements FirebaseService {
.build();
@Override
public void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body, Map<String, String> data) throws FirebaseMessagingException {
public void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body,
Map<String, String> data, Integer badge) throws FirebaseMessagingException {
FirebaseContext firebaseContext = contexts.asMap().compute(tenantId.toString(), (key, context) -> {
if (context == null) {
return new FirebaseContext(key, credentials);
@ -64,6 +65,13 @@ public class DefaultFirebaseService implements FirebaseService {
}
});
Aps.Builder apsConfig = Aps.builder()
.setContentAvailable(true)
.setSound("default");
if (badge != null) {
apsConfig.setBadge(badge);
}
Message message = Message.builder()
.setToken(fcmToken)
.setNotification(Notification.builder()
@ -74,14 +82,17 @@ public class DefaultFirebaseService implements FirebaseService {
.setPriority(AndroidConfig.Priority.HIGH)
.build())
.setApnsConfig(ApnsConfig.builder()
.setAps(Aps.builder()
.setContentAvailable(true)
.build())
.setAps(apsConfig.build())
.build())
.putAllData(data)
.build();
firebaseContext.getMessaging().send(message);
log.trace("[{}] Sent message for FCM token {}", tenantId, fcmToken);
try {
firebaseContext.getMessaging().send(message);
log.trace("[{}] Sent message for FCM token {}", tenantId, fcmToken);
} catch (Throwable t) {
log.debug("[{}] Failed to send message for FCM token {}", tenantId, fcmToken, t);
throw t;
}
}
public static class FirebaseContext {

8
application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java

@ -51,6 +51,8 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.notification.NotificationDeliveryMethod.WEB;
@Service
@TbCoreComponent
@RequiredArgsConstructor
@ -104,7 +106,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void fetchUnreadNotifications(NotificationsSubscription subscription) {
log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId());
PageData<Notification> notifications = notificationService.findLatestUnreadNotificationsByRecipientId(subscription.getTenantId(),
(UserId) subscription.getEntityId(), subscription.getLimit());
WEB, (UserId) subscription.getEntityId(), subscription.getLimit());
subscription.getLatestUnreadNotifications().clear();
notifications.getData().forEach(notification -> {
subscription.getLatestUnreadNotifications().put(notification.getUuidId(), notification);
@ -114,7 +116,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
private void fetchUnreadNotificationsCount(NotificationsCountSubscription subscription) {
log.trace("[{}, subId: {}] Fetching unread notifications count from DB", subscription.getSessionId(), subscription.getSubscriptionId());
int unreadCount = notificationService.countUnreadNotificationsByRecipientId(subscription.getTenantId(), (UserId) subscription.getEntityId());
int unreadCount = notificationService.countUnreadNotificationsByRecipientId(subscription.getTenantId(), WEB, (UserId) subscription.getEntityId());
subscription.getTotalUnreadCounter().set(unreadCount);
}
@ -235,7 +237,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
@Override
public void handleMarkAllAsReadCmd(WebSocketSessionRef sessionRef, MarkAllNotificationsAsReadCmd cmd) {
SecurityUser securityCtx = sessionRef.getSecurityCtx();
notificationCenter.markAllNotificationsAsRead(securityCtx.getTenantId(), securityCtx.getId());
notificationCenter.markAllNotificationsAsRead(securityCtx.getTenantId(), WEB, securityCtx.getId());
}
@Override

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

@ -145,7 +145,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
protected NotificationRequest submitNotificationRequest(List<NotificationTargetId> targets, String text, int delayInSec, NotificationDeliveryMethod... deliveryMethods) {
if (deliveryMethods.length == 0) {
deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEB};
deliveryMethods = new NotificationDeliveryMethod[]{NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP};
}
NotificationTemplate notificationTemplate = createNotificationTemplate(DEFAULT_NOTIFICATION_TYPE, DEFAULT_NOTIFICATION_SUBJECT, text, deliveryMethods);
return submitNotificationRequest(targets, notificationTemplate.getId(), delayInSec);
@ -247,8 +247,12 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
}
protected List<Notification> getMyNotifications(boolean unreadOnly, int limit) throws Exception {
return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&", new TypeReference<PageData<Notification>>() {},
new PageLink(limit, 0), unreadOnly).getData();
return getMyNotifications(NotificationDeliveryMethod.WEB, unreadOnly, limit);
}
protected List<Notification> getMyNotifications(NotificationDeliveryMethod deliveryMethod, boolean unreadOnly, int limit) throws Exception {
return doGetTypedWithPageLink("/api/notifications?unreadOnly={unreadOnly}&deliveryMethod={deliveryMethod}&", new TypeReference<PageData<Notification>>() {},
new PageLink(limit, 0), unreadOnly, deliveryMethod).getData();
}
protected NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) {

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

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.NotificationRuleId;
@ -52,6 +53,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequestPrevie
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.AlarmCommentNotificationInfo;
import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.config.AlarmCommentNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.settings.MobileAppNotificationDeliveryMethodConfig;
@ -81,7 +83,6 @@ import org.thingsboard.server.common.data.notification.template.WebDeliveryMetho
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.notification.DefaultNotifications;
import org.thingsboard.server.dao.notification.NotificationDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.notification.channels.MicrosoftTeamsNotificationChannel;
import org.thingsboard.server.service.ws.notification.cmd.UnreadNotificationsUpdate;
@ -116,17 +117,25 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Autowired
private NotificationCenter notificationCenter;
@Autowired
private NotificationDao notificationDao;
@Autowired
private MicrosoftTeamsNotificationChannel microsoftTeamsNotificationChannel;
@MockBean
private FirebaseService firebaseService;
private static final String TEST_MOBILE_TOKEN = "tenantFcmToken";
@Before
public void beforeEach() throws Exception {
loginCustomerUser();
wsClient = getWsClient();
loginSysAdmin();
MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig();
config.setFirebaseServiceAccountCredentials("testCredentials");
saveNotificationSettings(config);
loginTenantAdmin();
mobileToken = TEST_MOBILE_TOKEN;
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
}
@Test
@ -242,7 +251,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
int notificationsCount = 20;
wsClient.registerWaitForUpdate(notificationsCount);
for (int i = 1; i <= notificationsCount; i++) {
submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB);
submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP);
}
wsClient.waitForUpdate(true);
assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(notificationsCount);
@ -306,7 +315,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
NotificationTarget target = createNotificationTarget(savedDifferentTenantUser.getId());
int notificationsCount = 20;
for (int i = 0; i < notificationsCount; i++) {
NotificationRequest request = submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB);
NotificationRequest request = submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP);
awaitNotificationRequest(request.getId());
}
List<NotificationRequest> requests = notificationRequestService.findNotificationRequestsByTenantIdAndOriginatorType(tenantId, EntityType.USER, new PageLink(100)).getData();
@ -500,7 +509,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test
public void testNotificationRequestInfo() throws Exception {
NotificationDeliveryMethod[] deliveryMethods = new NotificationDeliveryMethod[]{
NotificationDeliveryMethod.WEB
NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.MOBILE_APP
};
NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Test subject", "Test text", deliveryMethods);
NotificationTarget target = createNotificationTarget(tenantAdminUserId);
@ -525,7 +534,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
await().atMost(2, TimeUnit.SECONDS)
.until(() -> findNotificationRequest(notificationRequest.getId()).isSent());
NotificationRequestStats stats = getStats(notificationRequest.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.WEB)).hasValue(1);
}
@ -727,17 +735,12 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
@Test
public void testMobileAppNotifications() throws Exception {
loginSysAdmin();
MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig();
config.setFirebaseServiceAccountCredentials("testCredentials");
saveNotificationSettings(config);
loginCustomerUser();
mobileToken = "customerFcmToken";
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
loginTenantAdmin();
mobileToken = "tenantFcmToken1";
mobileToken = TEST_MOBILE_TOKEN;
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
mobileToken = "tenantFcmToken2";
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
@ -746,7 +749,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
loginTenantAdmin();
NotificationTarget target = createNotificationTarget(new AllUsersFilter());
NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Title", "Message", NotificationDeliveryMethod.MOBILE_APP);
NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Title", "Message", NotificationDeliveryMethod.MOBILE_APP, NotificationDeliveryMethod.WEB);
((MobileAppDeliveryMethodNotificationTemplate) template.getConfiguration().getDeliveryMethodsTemplates().get(NotificationDeliveryMethod.MOBILE_APP))
.setAdditionalConfig(JacksonUtil.newObjectNode().set("test", JacksonUtil.newObjectNode().put("test", "test")));
saveNotificationTemplate(template);
@ -758,11 +761,19 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
.contains("doesn't use the mobile app");
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("tenantFcmToken1"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))));
eq(TEST_MOBILE_TOKEN), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))), eq(1));
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("tenantFcmToken2"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))));
eq("tenantFcmToken2"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))), eq(1));
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("customerFcmToken"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))));
eq("customerFcmToken"), eq("Title"), eq("Message"), argThat(data -> "test".equals(data.get("test.test"))), eq(1));
assertThat(getMyNotifications(NotificationDeliveryMethod.MOBILE_APP, true, 10)).singleElement().satisfies(notification -> {
assertThat(notification.getDeliveryMethod()).isEqualTo(NotificationDeliveryMethod.MOBILE_APP);
assertThat(notification.getText()).isEqualTo("Message");
assertThat(notification.getSubject()).isEqualTo("Title");
});
assertThat(getMyNotifications(true, 10)).singleElement().satisfies(notification -> {
assertThat(notification.getDeliveryMethod()).isEqualTo(NotificationDeliveryMethod.WEB);
});
verifyNoMoreInteractions(firebaseService);
clearInvocations(firebaseService);
@ -770,26 +781,21 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
request = submitNotificationRequest(List.of(target.getId()), template.getId(), 0);
awaitNotificationRequest(request.getId());
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("tenantFcmToken1"), eq("Title"), eq("Message"), anyMap());
eq(TEST_MOBILE_TOKEN), eq("Title"), eq("Message"), anyMap(), eq(2));
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("customerFcmToken"), eq("Title"), eq("Message"), anyMap());
eq("customerFcmToken"), eq("Title"), eq("Message"), anyMap(), eq(2));
verifyNoMoreInteractions(firebaseService);
}
@Test
public void testMobileAppNotifications_ruleBased() throws Exception {
loginSysAdmin();
MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig();
config.setFirebaseServiceAccountCredentials("testCredentials");
saveNotificationSettings(config);
loginTenantAdmin();
mobileToken = "tenantFcmToken";
mobileToken = TEST_MOBILE_TOKEN;
doPost("/api/user/mobile/session", new MobileSessionInfo()).andExpect(status().isOk());
createNotificationRule(AlarmCommentNotificationRuleTriggerConfig.builder().onlyUserComments(true).build(),
DefaultNotifications.alarmComment.getSubject(), DefaultNotifications.alarmComment.getText(),
List.of(createNotificationTarget(tenantAdminUserId).getId()), NotificationDeliveryMethod.MOBILE_APP);
List.of(createNotificationTarget(tenantAdminUserId).getId()), NotificationDeliveryMethod.MOBILE_APP, NotificationDeliveryMethod.WEB);
Device device = createDevice("test", "test");
UUID alarmDashboardId = UUID.randomUUID();
@ -802,18 +808,21 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
.put("dashboardId", alarmDashboardId.toString()))
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
AlarmId alarmId = alarm.getId();
AlarmComment comment = new AlarmComment();
comment.setComment(JacksonUtil.newObjectNode()
.put("text", "text"));
doPost("/api/alarm/" + alarm.getId() + "/comment", comment, AlarmComment.class);
doPost("/api/alarm/" + alarmId + "/comment", comment, AlarmComment.class);
String expectedSubject = "Comment on 'test' alarm";
String expectedBody = TENANT_ADMIN_EMAIL + " added comment: text";
ArgumentCaptor<Map<String, String>> msgCaptor = ArgumentCaptor.forClass(Map.class);
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("tenantFcmToken"), eq("Comment on 'test' alarm"),
eq(TENANT_ADMIN_EMAIL + " added comment: text"),
msgCaptor.capture());
eq(TEST_MOBILE_TOKEN), eq(expectedSubject),
eq(expectedBody),
msgCaptor.capture(), eq(1));
});
Map<String, String> firebaseMessageData = msgCaptor.getValue();
assertThat(firebaseMessageData.keySet()).doesNotContainNull().doesNotContain("");
@ -823,6 +832,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(firebaseMessageData.get("onClick.enabled")).isEqualTo("true");
assertThat(firebaseMessageData.get("onClick.linkType")).isEqualTo("DASHBOARD");
assertThat(firebaseMessageData.get("onClick.dashboardId")).isEqualTo(alarmDashboardId.toString());
assertThat(getMyNotifications(NotificationDeliveryMethod.MOBILE_APP, true, 10)).singleElement().satisfies(notification -> {
assertThat(notification.getDeliveryMethod()).isEqualTo(NotificationDeliveryMethod.MOBILE_APP);
assertThat(notification.getSubject()).isEqualTo(expectedSubject);
assertThat(notification.getText()).isEqualTo(expectedBody);
assertThat(notification.getInfo()).asInstanceOf(type(AlarmCommentNotificationInfo.class))
.matches(info -> info.getAlarmId().equals(alarmId.getId()) && info.getDashboardId().getId().equals(alarmDashboardId));
});
}
@Test

9
common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java

@ -19,6 +19,7 @@ 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.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -30,13 +31,13 @@ public interface NotificationService {
boolean markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId);
int markAllNotificationsAsRead(TenantId tenantId, UserId recipientId);
int markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);
PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, UserId recipientId, boolean unreadOnly, PageLink pageLink);
PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, boolean unreadOnly, PageLink pageLink);
PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit);
PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, int limit);
int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId);
int countUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);
boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId);

3
common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java

@ -36,13 +36,12 @@ public class Notification extends BaseData<NotificationId> {
private NotificationRequestId requestId;
private UserId recipientId;
private NotificationType type;
private NotificationDeliveryMethod deliveryMethod;
private String subject;
private String text;
private JsonNode additionalConfig;
private NotificationInfo info;
private NotificationStatus status;
}

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -620,6 +620,7 @@ public class ModelConstants {
public static final String NOTIFICATION_REQUEST_ID_PROPERTY = "request_id";
public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id";
public static final String NOTIFICATION_TYPE_PROPERTY = "type";
public static final String NOTIFICATION_DELIVERY_METHOD_PROPERTY = "delivery_method";
public static final String NOTIFICATION_SUBJECT_PROPERTY = "subject";
public static final String NOTIFICATION_TEXT_PROPERTY = "body";
public static final String NOTIFICATION_ADDITIONAL_CONFIG_PROPERTY = "additional_config";

7
dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationId;
import org.thingsboard.server.common.data.id.NotificationRequestId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
@ -56,6 +57,10 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
@Column(name = ModelConstants.NOTIFICATION_TYPE_PROPERTY, nullable = false)
private NotificationType type;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.NOTIFICATION_DELIVERY_METHOD_PROPERTY, nullable = false)
private NotificationDeliveryMethod deliveryMethod;
@Column(name = ModelConstants.NOTIFICATION_SUBJECT_PROPERTY)
private String subject;
@ -82,6 +87,7 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
setRequestId(getUuid(notification.getRequestId()));
setRecipientId(getUuid(notification.getRecipientId()));
setType(notification.getType());
setDeliveryMethod(notification.getDeliveryMethod());
setSubject(notification.getSubject());
setText(notification.getText());
setAdditionalConfig(notification.getAdditionalConfig());
@ -97,6 +103,7 @@ public class NotificationEntity extends BaseSqlEntity<Notification> {
notification.setRequestId(getEntityId(requestId, NotificationRequestId::new));
notification.setRecipientId(getEntityId(recipientId, UserId::new));
notification.setType(type);
notification.setDeliveryMethod(deliveryMethod);
notification.setSubject(subject);
notification.setText(text);
notification.setAdditionalConfig(additionalConfig);

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

@ -25,6 +25,7 @@ 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.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -57,29 +58,29 @@ public class DefaultNotificationService implements NotificationService, EntityDa
}
@Override
public int markAllNotificationsAsRead(TenantId tenantId, UserId recipientId) {
return notificationDao.updateStatusByRecipientId(tenantId, recipientId, NotificationStatus.READ);
public int markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) {
return notificationDao.updateStatusByDeliveryMethodAndRecipientId(tenantId, deliveryMethod, recipientId, NotificationStatus.READ);
}
@Override
public PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, UserId recipientId, boolean unreadOnly, PageLink pageLink) {
public PageData<Notification> findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, boolean unreadOnly, PageLink pageLink) {
if (unreadOnly) {
return notificationDao.findUnreadByRecipientIdAndPageLink(tenantId, recipientId, pageLink);
return notificationDao.findUnreadByDeliveryMethodAndRecipientIdAndPageLink(tenantId, deliveryMethod, recipientId, pageLink);
} else {
return notificationDao.findByRecipientIdAndPageLink(tenantId, recipientId, pageLink);
return notificationDao.findByDeliveryMethodAndRecipientIdAndPageLink(tenantId, deliveryMethod, recipientId, pageLink);
}
}
@Override
public PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit) {
public PageData<Notification> findLatestUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, 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 findNotificationsByRecipientIdAndReadStatus(tenantId, deliveryMethod, recipientId, true, pageLink);
}
@Override
public int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId) {
return notificationDao.countUnreadByRecipientId(tenantId, recipientId);
public int countUnreadNotificationsByRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) {
return notificationDao.countUnreadByDeliveryMethodAndRecipientId(tenantId, deliveryMethod, recipientId);
}
@Override

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

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId;
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.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -27,19 +28,17 @@ import org.thingsboard.server.dao.Dao;
public interface NotificationDao extends Dao<Notification> {
PageData<Notification> findUnreadByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink);
PageData<Notification> findUnreadByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink);
PageData<Notification> findByRecipientIdAndPageLink(TenantId tenantId, 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);
int countUnreadByRecipientId(TenantId tenantId, UserId recipientId);
PageData<Notification> findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink);
int countUnreadByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);
boolean deleteByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId);
int updateStatusByRecipientId(TenantId tenantId, UserId recipientId, NotificationStatus status);
int updateStatusByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, NotificationStatus status);
void deleteByRequestId(TenantId tenantId, NotificationRequestId requestId);

24
dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationRequestId;
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.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -51,14 +52,14 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao<NotificationEn
private int partitionSizeInHours;
@Override
public PageData<Notification> findUnreadByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByRecipientIdAndStatusNot(recipientId.getId(), NotificationStatus.READ,
pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
public PageData<Notification> findUnreadByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientIdAndStatusNot(deliveryMethod,
recipientId.getId(), NotificationStatus.READ, pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<Notification> findByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByRecipientId(recipientId.getId(),
public PageData<Notification> findByDeliveryMethodAndRecipientIdAndPageLink(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByDeliveryMethodAndRecipientId(deliveryMethod, recipientId.getId(),
pageLink.getTextSearch(), DaoUtil.toPageable(pageLink)));
}
@ -71,13 +72,8 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao<NotificationEn
* For this hot method, the partial index `idx_notification_recipient_id_unread` was introduced since 3.6.0
* */
@Override
public int countUnreadByRecipientId(TenantId tenantId, UserId recipientId) {
return notificationRepository.countByRecipientIdAndStatusNot(recipientId.getId(), NotificationStatus.READ);
}
@Override
public PageData<Notification> findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink) {
return DaoUtil.toPageData(notificationRepository.findByRequestId(notificationRequestId.getId(), DaoUtil.toPageable(pageLink)));
public int countUnreadByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId) {
return notificationRepository.countByDeliveryMethodAndRecipientIdAndStatusNot(deliveryMethod, recipientId.getId(), NotificationStatus.READ);
}
@Override
@ -86,8 +82,8 @@ public class JpaNotificationDao extends JpaPartitionedAbstractDao<NotificationEn
}
@Override
public int updateStatusByRecipientId(TenantId tenantId, UserId recipientId, NotificationStatus status) {
return notificationRepository.updateStatusByRecipientId(recipientId.getId(), status);
public int updateStatusByDeliveryMethodAndRecipientId(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId, NotificationStatus status) {
return notificationRepository.updateStatusByDeliveryMethodAndRecipientIdAndStatusNot(deliveryMethod, recipientId.getId(), status);
}
@Override

33
dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java

@ -23,6 +23,7 @@ import org.springframework.data.jpa.repository.Query;
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.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.NotificationStatus;
import org.thingsboard.server.dao.model.sql.NotificationEntity;
@ -31,20 +32,23 @@ import java.util.UUID;
@Repository
public interface NotificationRepository extends JpaRepository<NotificationEntity, UUID> {
@Query("SELECT n FROM NotificationEntity n WHERE n.recipientId = :recipientId AND n.status <> :status " +
@Query("SELECT n FROM NotificationEntity n WHERE n.deliveryMethod = :deliveryMethod " +
"AND n.recipientId = :recipientId AND n.status <> :status " +
"AND (:searchText is NULL OR ilike(n.subject, concat('%', :searchText, '%')) = true " +
"OR ilike(n.text, concat('%', :searchText, '%')) = true)")
Page<NotificationEntity> findByRecipientIdAndStatusNot(@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status,
@Param("searchText") String searchText,
Pageable pageable);
Page<NotificationEntity> findByDeliveryMethodAndRecipientIdAndStatusNot(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod,
@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT n FROM NotificationEntity n WHERE n.recipientId = :recipientId " +
@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 " +
"OR ilike(n.text, concat('%', :searchText, '%')) = true)")
Page<NotificationEntity> findByRecipientId(@Param("recipientId") UUID recipientId,
@Param("searchText") String searchText,
Pageable pageable);
Page<NotificationEntity> findByDeliveryMethodAndRecipientId(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod,
@Param("recipientId") UUID recipientId,
@Param("searchText") String searchText,
Pageable pageable);
@Modifying
@Transactional
@ -54,9 +58,7 @@ public interface NotificationRepository extends JpaRepository<NotificationEntity
@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status);
int countByRecipientIdAndStatusNot(UUID recipientId, NotificationStatus status);
Page<NotificationEntity> findByRequestId(UUID requestId, Pageable pageable);
int countByDeliveryMethodAndRecipientIdAndStatusNot(NotificationDeliveryMethod deliveryMethod, UUID recipientId, NotificationStatus status);
@Transactional
@Modifying
@ -76,8 +78,9 @@ public interface NotificationRepository extends JpaRepository<NotificationEntity
@Modifying
@Transactional
@Query("UPDATE NotificationEntity n SET n.status = :status " +
"WHERE n.recipientId = :recipientId AND n.status <> :status")
int updateStatusByRecipientId(@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status);
"WHERE n.deliveryMethod = :deliveryMethod AND n.recipientId = :recipientId AND n.status <> :status")
int updateStatusByDeliveryMethodAndRecipientIdAndStatusNot(@Param("deliveryMethod") NotificationDeliveryMethod deliveryMethod,
@Param("recipientId") UUID recipientId,
@Param("status") NotificationStatus status);
}

4
dao/src/main/resources/sql/schema-entities-idx.sql

@ -118,9 +118,9 @@ CREATE INDEX IF NOT EXISTS idx_notification_id ON notification(id);
CREATE INDEX IF NOT EXISTS idx_notification_notification_request_id ON notification(request_id);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_created_time ON notification(recipient_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_created_time ON notification(delivery_method, recipient_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';
CREATE INDEX IF NOT EXISTS idx_notification_delivery_method_recipient_id_unread ON notification(delivery_method, recipient_id) WHERE status <> 'READ';
CREATE INDEX IF NOT EXISTS idx_resource_etag ON resource(tenant_id, etag);

1
dao/src/main/resources/sql/schema-entities.sql

@ -864,6 +864,7 @@ CREATE TABLE IF NOT EXISTS notification (
request_id UUID,
recipient_id UUID NOT NULL,
type VARCHAR(50) NOT NULL,
delivery_method VARCHAR(50) NOT NULL,
subject VARCHAR(255),
body VARCHAR(1000) NOT NULL,
additional_config VARCHAR(1000),

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NotificationCenter.java

@ -38,7 +38,7 @@ public interface NotificationCenter {
void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId);
void markAllNotificationsAsRead(TenantId tenantId, UserId recipientId);
void markAllNotificationsAsRead(TenantId tenantId, NotificationDeliveryMethod deliveryMethod, UserId recipientId);
void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId);

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java

@ -21,6 +21,6 @@ import java.util.Map;
public interface FirebaseService {
void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body, Map<String, String> data) throws Exception;
void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body, Map<String, String> data, Integer badge) throws Exception;
}

Loading…
Cancel
Save