Notification system fixes and improvements
This commit is contained in:
parent
5c8d9e9f8d
commit
33dca7af71
@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.notification.NotificationRequest;
|
|||||||
import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
|
import org.thingsboard.server.common.data.notification.NotificationRequestInfo;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
|
import org.thingsboard.server.common.data.notification.NotificationRequestPreview;
|
||||||
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
|
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
|
||||||
|
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
|
||||||
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
|
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
|
||||||
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
|
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
|
||||||
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
|
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
|
||||||
@ -62,7 +63,6 @@ import org.thingsboard.server.service.security.permission.Operation;
|
|||||||
import org.thingsboard.server.service.security.permission.Resource;
|
import org.thingsboard.server.service.security.permission.Resource;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -215,19 +215,17 @@ public class NotificationController extends BaseController {
|
|||||||
NotificationProcessingContext tmpProcessingCtx = NotificationProcessingContext.builder()
|
NotificationProcessingContext tmpProcessingCtx = NotificationProcessingContext.builder()
|
||||||
.tenantId(user.getTenantId())
|
.tenantId(user.getTenantId())
|
||||||
.request(request)
|
.request(request)
|
||||||
.settings(null)
|
|
||||||
.template(template)
|
.template(template)
|
||||||
|
.settings(null)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = tmpProcessingCtx.getDeliveryMethods().stream()
|
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = tmpProcessingCtx.getDeliveryMethods().stream()
|
||||||
.collect(Collectors.toMap(m -> m, deliveryMethod -> {
|
.collect(Collectors.toMap(m -> m, deliveryMethod -> {
|
||||||
Map<String, String> templateContext;
|
NotificationRecipient recipient = null;
|
||||||
if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) {
|
if (NotificationTargetType.PLATFORM_USERS.getSupportedDeliveryMethods().contains(deliveryMethod)) {
|
||||||
templateContext = tmpProcessingCtx.createTemplateContext(user);
|
recipient = userService.findUserById(user.getTenantId(), user.getId());
|
||||||
} else {
|
|
||||||
templateContext = Collections.emptyMap();
|
|
||||||
}
|
}
|
||||||
return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, templateContext);
|
return tmpProcessingCtx.getProcessedTemplate(deliveryMethod, recipient);
|
||||||
}));
|
}));
|
||||||
preview.setProcessedTemplates(processedTemplates);
|
preview.setProcessedTemplates(processedTemplates);
|
||||||
|
|
||||||
|
|||||||
@ -152,8 +152,8 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
|
|||||||
NotificationProcessingContext ctx = NotificationProcessingContext.builder()
|
NotificationProcessingContext ctx = NotificationProcessingContext.builder()
|
||||||
.tenantId(tenantId)
|
.tenantId(tenantId)
|
||||||
.request(request)
|
.request(request)
|
||||||
.settings(settings)
|
|
||||||
.template(notificationTemplate)
|
.template(notificationTemplate)
|
||||||
|
.settings(settings)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
notificationExecutor.submit(() -> {
|
notificationExecutor.submit(() -> {
|
||||||
@ -230,15 +230,10 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
|
|||||||
if (ctx.getStats().contains(deliveryMethod, recipient.getId())) {
|
if (ctx.getStats().contains(deliveryMethod, recipient.getId())) {
|
||||||
return Futures.immediateFailedFuture(new AlreadySentException());
|
return Futures.immediateFailedFuture(new AlreadySentException());
|
||||||
}
|
}
|
||||||
Map<String, String> templateContext;
|
|
||||||
if (recipient instanceof User) {
|
|
||||||
templateContext = ctx.createTemplateContext(((User) recipient));
|
|
||||||
} else {
|
|
||||||
templateContext = Collections.emptyMap();
|
|
||||||
}
|
|
||||||
DeliveryMethodNotificationTemplate processedTemplate;
|
DeliveryMethodNotificationTemplate processedTemplate;
|
||||||
try {
|
try {
|
||||||
processedTemplate = ctx.getProcessedTemplate(deliveryMethod, templateContext);
|
processedTemplate = ctx.getProcessedTemplate(deliveryMethod, recipient);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Futures.immediateFailedFuture(e);
|
return Futures.immediateFailedFuture(e);
|
||||||
}
|
}
|
||||||
@ -300,7 +295,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
|
|||||||
log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId);
|
log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId);
|
||||||
NotificationUpdate update = NotificationUpdate.builder()
|
NotificationUpdate update = NotificationUpdate.builder()
|
||||||
.updated(true)
|
.updated(true)
|
||||||
.notificationId(notificationId)
|
.notificationId(notificationId.getId())
|
||||||
.newStatus(NotificationStatus.READ)
|
.newStatus(NotificationStatus.READ)
|
||||||
.build();
|
.build();
|
||||||
onNotificationUpdate(tenantId, recipientId, update);
|
onNotificationUpdate(tenantId, recipientId, update);
|
||||||
|
|||||||
@ -15,12 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.server.service.notification;
|
package org.thingsboard.server.service.notification;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.TextNode;
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import org.apache.commons.collections4.MapUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.thingsboard.server.common.data.User;
|
import org.thingsboard.server.common.data.User;
|
||||||
import org.thingsboard.server.common.data.id.CustomerId;
|
import org.thingsboard.server.common.data.id.CustomerId;
|
||||||
@ -28,23 +26,25 @@ import org.thingsboard.server.common.data.id.TenantId;
|
|||||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationRequest;
|
import org.thingsboard.server.common.data.notification.NotificationRequest;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
|
import org.thingsboard.server.common.data.notification.NotificationRequestStats;
|
||||||
import org.thingsboard.server.common.data.notification.info.NotificationInfo;
|
|
||||||
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
|
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
|
||||||
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
|
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
|
||||||
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
|
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
|
||||||
|
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
|
||||||
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
|
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
|
||||||
import org.thingsboard.server.common.data.notification.template.HasSubject;
|
import org.thingsboard.server.common.data.notification.template.HasSubject;
|
||||||
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
|
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
|
||||||
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
|
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
|
||||||
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
|
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public class NotificationProcessingContext {
|
public class NotificationProcessingContext {
|
||||||
|
|
||||||
@ -65,8 +65,7 @@ public class NotificationProcessingContext {
|
|||||||
private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\$\\{([a-zA-Z]+)(:[a-zA-Z]+)?}");
|
private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\$\\{([a-zA-Z]+)(:[a-zA-Z]+)?}");
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationSettings settings,
|
public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationTemplate template, NotificationSettings settings) {
|
||||||
NotificationTemplate template) {
|
|
||||||
this.tenantId = tenantId;
|
this.tenantId = tenantId;
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
@ -80,6 +79,7 @@ public class NotificationProcessingContext {
|
|||||||
NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration();
|
NotificationTemplateConfig templateConfig = notificationTemplate.getConfiguration();
|
||||||
templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
|
templateConfig.getDeliveryMethodsTemplates().forEach((deliveryMethod, template) -> {
|
||||||
if (template.isEnabled()) {
|
if (template.isEnabled()) {
|
||||||
|
template = processTemplate(template, null); // processing template with immutable params
|
||||||
templates.put(deliveryMethod, template);
|
templates.put(deliveryMethod, template);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -90,36 +90,43 @@ public class NotificationProcessingContext {
|
|||||||
return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod);
|
return (C) settings.getDeliveryMethodsConfigs().get(deliveryMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, Map<String, String> templateContext) {
|
public <T extends DeliveryMethodNotificationTemplate> T getProcessedTemplate(NotificationDeliveryMethod deliveryMethod, NotificationRecipient recipient) {
|
||||||
NotificationInfo info = request.getInfo();
|
T template = (T) templates.get(deliveryMethod);
|
||||||
if (info != null) {
|
Map<String, String> additionalTemplateContext = null;
|
||||||
templateContext = new HashMap<>(templateContext);
|
if (recipient != null) {
|
||||||
templateContext.putAll(info.getTemplateData());
|
additionalTemplateContext = createTemplateContextForRecipient(recipient);
|
||||||
}
|
}
|
||||||
|
if (MapUtils.isNotEmpty(additionalTemplateContext) && template.containsAny(additionalTemplateContext.keySet().toArray(String[]::new))) {
|
||||||
|
template = processTemplate(template, additionalTemplateContext);
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
T template = (T) templates.get(deliveryMethod).copy();
|
private <T extends DeliveryMethodNotificationTemplate> T processTemplate(T template, Map<String, String> additionalTemplateContext) {
|
||||||
|
Map<String, String> templateContext = new HashMap<>();
|
||||||
|
if (request.getInfo() != null) {
|
||||||
|
templateContext.putAll(request.getInfo().getTemplateData());
|
||||||
|
}
|
||||||
|
if (additionalTemplateContext != null) {
|
||||||
|
templateContext.putAll(additionalTemplateContext);
|
||||||
|
}
|
||||||
|
if (templateContext.isEmpty()) return template;
|
||||||
|
|
||||||
|
template = (T) template.copy();
|
||||||
template.setBody(processTemplate(template.getBody(), templateContext));
|
template.setBody(processTemplate(template.getBody(), templateContext));
|
||||||
if (template instanceof HasSubject) {
|
if (template instanceof HasSubject) {
|
||||||
String subject = ((HasSubject) template).getSubject();
|
String subject = ((HasSubject) template).getSubject();
|
||||||
((HasSubject) template).setSubject(processTemplate(subject, templateContext));
|
((HasSubject) template).setSubject(processTemplate(subject, templateContext));
|
||||||
}
|
}
|
||||||
|
if (template instanceof WebDeliveryMethodNotificationTemplate) {
|
||||||
if (deliveryMethod == NotificationDeliveryMethod.WEB) {
|
|
||||||
WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template;
|
WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template;
|
||||||
Optional<ObjectNode> buttonConfig = Optional.ofNullable(webNotificationTemplate.getAdditionalConfig())
|
String buttonText = webNotificationTemplate.getButtonText();
|
||||||
.map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject)
|
if (isNotEmpty(buttonText)) {
|
||||||
.map(config -> (ObjectNode) config);
|
webNotificationTemplate.setButtonText(processTemplate(buttonText, templateContext));
|
||||||
if (buttonConfig.isPresent()) {
|
}
|
||||||
JsonNode text = buttonConfig.get().get("text");
|
String buttonLink = webNotificationTemplate.getButtonLink();
|
||||||
if (text != null && text.isTextual()) {
|
if (isNotEmpty(buttonLink)) {
|
||||||
text = new TextNode(processTemplate(text.asText(), templateContext));
|
webNotificationTemplate.setButtonLink(processTemplate(buttonLink, templateContext));
|
||||||
buttonConfig.get().set("text", text);
|
|
||||||
}
|
|
||||||
JsonNode link = buttonConfig.get().get("link");
|
|
||||||
if (link != null && link.isTextual()) {
|
|
||||||
link = new TextNode(processTemplate(link.asText(), templateContext));
|
|
||||||
buttonConfig.get().set("link", link);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return template;
|
return template;
|
||||||
@ -128,6 +135,9 @@ public class NotificationProcessingContext {
|
|||||||
private static String processTemplate(String template, Map<String, String> context) {
|
private static String processTemplate(String template, Map<String, String> context) {
|
||||||
return TEMPLATE_PARAM_PATTERN.matcher(template).replaceAll(matchResult -> {
|
return TEMPLATE_PARAM_PATTERN.matcher(template).replaceAll(matchResult -> {
|
||||||
String key = matchResult.group(1);
|
String key = matchResult.group(1);
|
||||||
|
if (!context.containsKey(key)) {
|
||||||
|
return "\\" + matchResult.group(); // adding escape char due to special meaning of '$' to matcher
|
||||||
|
}
|
||||||
String value = Strings.nullToEmpty(context.get(key));
|
String value = Strings.nullToEmpty(context.get(key));
|
||||||
String function = matchResult.group(2);
|
String function = matchResult.group(2);
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
@ -144,12 +154,16 @@ public class NotificationProcessingContext {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> createTemplateContext(User recipient) {
|
private Map<String, String> createTemplateContextForRecipient(NotificationRecipient recipient) {
|
||||||
Map<String, String> templateContext = new HashMap<>();
|
if (recipient instanceof User) {
|
||||||
templateContext.put("recipientEmail", recipient.getEmail());
|
User user = (User) recipient;
|
||||||
templateContext.put("recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()));
|
return Map.of(
|
||||||
templateContext.put("recipientLastName", Strings.nullToEmpty(recipient.getLastName()));
|
"recipientEmail", user.getEmail(),
|
||||||
return templateContext;
|
"recipientFirstName", Strings.nullToEmpty(user.getFirstName()),
|
||||||
|
"recipientLastName", Strings.nullToEmpty(user.getLastName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomerId getCustomerId() {
|
public CustomerId getCustomerId() {
|
||||||
|
|||||||
@ -120,17 +120,21 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
|
|||||||
|
|
||||||
/* Notifications subscription update handling */
|
/* Notifications subscription update handling */
|
||||||
private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
|
private void handleNotificationsSubscriptionUpdate(NotificationsSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
|
||||||
if (subscriptionUpdate.getNotificationUpdate() != null) {
|
try {
|
||||||
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
|
if (subscriptionUpdate.getNotificationUpdate() != null) {
|
||||||
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
|
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
|
||||||
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
|
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
|
||||||
|
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[{}, subId: {}] Failed to handle update for notifications subscription: {}", subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) {
|
private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) {
|
||||||
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().getId();
|
UUID notificationId = notification != null ? notification.getUuidId() : update.getNotificationId();
|
||||||
if (update.isCreated()) {
|
if (update.isCreated()) {
|
||||||
subscription.getLatestUnreadNotifications().put(notificationId, notification);
|
subscription.getLatestUnreadNotifications().put(notificationId, notification);
|
||||||
subscription.getTotalUnreadCounter().incrementAndGet();
|
subscription.getTotalUnreadCounter().incrementAndGet();
|
||||||
@ -175,10 +179,14 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
|
|||||||
|
|
||||||
/* Notifications count subscription update handling */
|
/* Notifications count subscription update handling */
|
||||||
private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
|
private void handleNotificationsCountSubscriptionUpdate(NotificationsCountSubscription subscription, NotificationsSubscriptionUpdate subscriptionUpdate) {
|
||||||
if (subscriptionUpdate.getNotificationUpdate() != null) {
|
try {
|
||||||
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
|
if (subscriptionUpdate.getNotificationUpdate() != null) {
|
||||||
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
|
handleNotificationUpdate(subscription, subscriptionUpdate.getNotificationUpdate());
|
||||||
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
|
} else if (subscriptionUpdate.getNotificationRequestUpdate() != null) {
|
||||||
|
handleNotificationRequestUpdate(subscription, subscriptionUpdate.getNotificationRequestUpdate());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[{}, subId: {}] Failed to handle update for notifications count subscription: {}", subscription.getSessionId(), subscription.getSubscriptionId(), subscriptionUpdate, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,17 +19,18 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.thingsboard.server.common.data.id.NotificationId;
|
|
||||||
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 java.util.UUID;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@Builder
|
@Builder
|
||||||
public class NotificationUpdate {
|
public class NotificationUpdate {
|
||||||
|
|
||||||
private NotificationId notificationId;
|
private UUID notificationId;
|
||||||
|
|
||||||
private boolean created;
|
private boolean created;
|
||||||
private Notification notification;
|
private Notification notification;
|
||||||
|
|||||||
@ -15,12 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.server.service.notification;
|
package org.thingsboard.server.service.notification;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.assertj.core.data.Offset;
|
import org.assertj.core.data.Offset;
|
||||||
import org.java_websocket.client.WebSocketClient;
|
import org.java_websocket.client.WebSocketClient;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.thingsboard.rule.engine.api.NotificationCenter;
|
import org.thingsboard.rule.engine.api.NotificationCenter;
|
||||||
@ -39,7 +37,6 @@ import org.thingsboard.server.common.data.notification.NotificationType;
|
|||||||
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
|
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
|
||||||
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
|
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
|
||||||
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
|
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
|
||||||
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
|
|
||||||
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
|
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
|
||||||
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
|
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
|
||||||
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
|
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
|
||||||
@ -52,8 +49,6 @@ import org.thingsboard.server.common.data.notification.template.NotificationTemp
|
|||||||
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
|
import org.thingsboard.server.common.data.notification.template.SlackDeliveryMethodNotificationTemplate;
|
||||||
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
|
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
|
||||||
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
|
import org.thingsboard.server.common.data.notification.template.WebDeliveryMethodNotificationTemplate;
|
||||||
import org.thingsboard.server.common.data.page.PageData;
|
|
||||||
import org.thingsboard.server.common.data.page.PageLink;
|
|
||||||
import org.thingsboard.server.common.data.security.Authority;
|
import org.thingsboard.server.common.data.security.Authority;
|
||||||
import org.thingsboard.server.dao.DaoUtil;
|
import org.thingsboard.server.dao.DaoUtil;
|
||||||
import org.thingsboard.server.dao.notification.NotificationDao;
|
import org.thingsboard.server.dao.notification.NotificationDao;
|
||||||
@ -66,8 +61,6 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
@ -377,7 +370,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
|||||||
|
|
||||||
WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
|
WebDeliveryMethodNotificationTemplate webNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
|
||||||
webNotificationTemplate.setEnabled(true);
|
webNotificationTemplate.setEnabled(true);
|
||||||
webNotificationTemplate.setBody("Message for WEB: ${recipientEmail}");
|
webNotificationTemplate.setBody("Message for WEB: ${recipientEmail} ${unknownParam}");
|
||||||
webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}");
|
webNotificationTemplate.setSubject("Subject for WEB: ${recipientEmail}");
|
||||||
templates.put(NotificationDeliveryMethod.WEB, webNotificationTemplate);
|
templates.put(NotificationDeliveryMethod.WEB, webNotificationTemplate);
|
||||||
|
|
||||||
@ -416,7 +409,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
|||||||
.satisfies(template -> {
|
.satisfies(template -> {
|
||||||
assertThat(template.getBody())
|
assertThat(template.getBody())
|
||||||
.startsWith("Message for WEB")
|
.startsWith("Message for WEB")
|
||||||
.endsWith(requestorEmail);
|
.endsWith(requestorEmail + " ${unknownParam}");
|
||||||
assertThat(template.getSubject())
|
assertThat(template.getSubject())
|
||||||
.startsWith("Subject for WEB")
|
.startsWith("Subject for WEB")
|
||||||
.endsWith(requestorEmail);
|
.endsWith(requestorEmail);
|
||||||
@ -439,7 +432,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
|||||||
assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class))
|
assertThat(processedTemplates.get(NotificationDeliveryMethod.SLACK)).asInstanceOf(type(SlackDeliveryMethodNotificationTemplate.class))
|
||||||
.satisfies(template -> {
|
.satisfies(template -> {
|
||||||
assertThat(template.getBody())
|
assertThat(template.getBody())
|
||||||
.isEqualTo("Message for SLACK: "); // ${recipientEmail} should be removed
|
.isEqualTo("Message for SLACK: ${recipientEmail}"); // ${recipientEmail} should not be processed
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,53 +469,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
|
||||||
public void testNotificationsForALotOfUsers() throws Exception {
|
|
||||||
int usersCount = 5000;
|
|
||||||
|
|
||||||
List<User> users = new ArrayList<>();
|
|
||||||
for (int i = 1; i <= usersCount; i++) {
|
|
||||||
User user = new User();
|
|
||||||
user.setTenantId(tenantId);
|
|
||||||
user.setAuthority(Authority.TENANT_ADMIN);
|
|
||||||
user.setEmail("test-user-" + i + "@thingsboard.org");
|
|
||||||
user = doPost("/api/user", user, User.class);
|
|
||||||
users.add(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationTarget notificationTarget = new NotificationTarget();
|
|
||||||
notificationTarget.setTenantId(tenantId);
|
|
||||||
notificationTarget.setName("All my users");
|
|
||||||
PlatformUsersNotificationTargetConfig config = new PlatformUsersNotificationTargetConfig();
|
|
||||||
AllUsersFilter filter = new AllUsersFilter();
|
|
||||||
config.setUsersFilter(filter);
|
|
||||||
notificationTarget.setConfiguration(config);
|
|
||||||
notificationTarget = saveNotificationTarget(notificationTarget);
|
|
||||||
NotificationTargetId notificationTargetId = notificationTarget.getId();
|
|
||||||
|
|
||||||
ListenableFuture<NotificationRequest> request = executor.submit(() -> {
|
|
||||||
return submitNotificationRequest(notificationTargetId, "Hello, ${recipientEmail}", 0, NotificationDeliveryMethod.WEB);
|
|
||||||
});
|
|
||||||
await().atMost(10, TimeUnit.SECONDS).until(request::isDone);
|
|
||||||
NotificationRequest notificationRequest = request.get();
|
|
||||||
|
|
||||||
await().atMost(5, TimeUnit.SECONDS)
|
|
||||||
.pollInterval(200, TimeUnit.MILLISECONDS)
|
|
||||||
.until(() -> {
|
|
||||||
PageData<Notification> sentNotifications = notificationDao.findByRequestId(tenantId, notificationRequest.getId(), new PageLink(1));
|
|
||||||
return sentNotifications.getTotalElements() >= usersCount;
|
|
||||||
});
|
|
||||||
|
|
||||||
PageData<Notification> sentNotifications = notificationDao.findByRequestId(tenantId, notificationRequest.getId(), new PageLink(Integer.MAX_VALUE));
|
|
||||||
assertThat(sentNotifications.getData()).extracting(Notification::getRecipientId)
|
|
||||||
.containsAll(users.stream().map(User::getId).collect(Collectors.toSet()));
|
|
||||||
|
|
||||||
NotificationRequestStats stats = getStats(notificationRequest.getId());
|
|
||||||
assertThat(stats.getSent().values().stream().mapToInt(AtomicInteger::get).sum()).isGreaterThanOrEqualTo(usersCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void testSlackNotifications() throws Exception {
|
public void testSlackNotifications() throws Exception {
|
||||||
NotificationSettings settings = new NotificationSettings();
|
NotificationSettings settings = new NotificationSettings();
|
||||||
SlackNotificationDeliveryMethodConfig slackConfig = new SlackNotificationDeliveryMethodConfig();
|
SlackNotificationDeliveryMethodConfig slackConfig = new SlackNotificationDeliveryMethodConfig();
|
||||||
@ -559,7 +505,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
|||||||
NotificationRequest successfulNotificationRequest = submitNotificationRequest(List.of(notificationTarget.getId()), notificationTemplate.getId(), 0);
|
NotificationRequest successfulNotificationRequest = submitNotificationRequest(List.of(notificationTarget.getId()), notificationTemplate.getId(), 0);
|
||||||
await().atMost(2, TimeUnit.SECONDS)
|
await().atMost(2, TimeUnit.SECONDS)
|
||||||
.until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent());
|
.until(() -> findNotificationRequest(successfulNotificationRequest.getId()).isSent());
|
||||||
verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq("To Slack :) "));
|
verify(slackService).sendMessage(eq(tenantId), eq(slackToken), eq(conversationId), eq(slackNotificationTemplate.getBody()));
|
||||||
NotificationRequestStats stats = getStats(successfulNotificationRequest.getId());
|
NotificationRequestStats stats = getStats(successfulNotificationRequest.getId());
|
||||||
assertThat(stats.getSent().get(NotificationDeliveryMethod.SLACK)).hasValue(1);
|
assertThat(stats.getSent().get(NotificationDeliveryMethod.SLACK)).hasValue(1);
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import lombok.NoArgsConstructor;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
||||||
|
|
||||||
import javax.validation.constraints.AssertTrue;
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
@ -55,4 +54,8 @@ public abstract class DeliveryMethodNotificationTemplate {
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public abstract DeliveryMethodNotificationTemplate copy();
|
public abstract DeliveryMethodNotificationTemplate copy();
|
||||||
|
|
||||||
|
public boolean containsAny(String... params) {
|
||||||
|
return StringUtils.containsAny(body, params);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import lombok.Data;
|
|||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
||||||
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
@ -47,4 +48,9 @@ public class EmailDeliveryMethodNotificationTemplate extends DeliveryMethodNotif
|
|||||||
return new EmailDeliveryMethodNotificationTemplate(this);
|
return new EmailDeliveryMethodNotificationTemplate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAny(String... params) {
|
||||||
|
return super.containsAny(params) || StringUtils.containsAny(subject, params);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,14 +15,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.server.common.data.notification.template;
|
package org.thingsboard.server.common.data.notification.template;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
||||||
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@ -37,7 +42,44 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific
|
|||||||
public WebDeliveryMethodNotificationTemplate(WebDeliveryMethodNotificationTemplate other) {
|
public WebDeliveryMethodNotificationTemplate(WebDeliveryMethodNotificationTemplate other) {
|
||||||
super(other);
|
super(other);
|
||||||
this.subject = other.subject;
|
this.subject = other.subject;
|
||||||
this.additionalConfig = other.additionalConfig;
|
this.additionalConfig = other.additionalConfig != null ? other.additionalConfig.deepCopy() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public String getButtonText() {
|
||||||
|
return getButtonConfigProperty("text");
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setButtonText(String buttonText) {
|
||||||
|
getButtonConfig().ifPresent(buttonConfig -> {
|
||||||
|
buttonConfig.set("text", new TextNode(buttonText));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public String getButtonLink() {
|
||||||
|
return getButtonConfigProperty("link");
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public void setButtonLink(String buttonLink) {
|
||||||
|
getButtonConfig().ifPresent(buttonConfig -> {
|
||||||
|
buttonConfig.set("link", new TextNode(buttonLink));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getButtonConfigProperty(String property) {
|
||||||
|
return getButtonConfig()
|
||||||
|
.map(buttonConfig -> buttonConfig.get(property))
|
||||||
|
.filter(JsonNode::isTextual)
|
||||||
|
.map(JsonNode::asText).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ObjectNode> getButtonConfig() {
|
||||||
|
return Optional.ofNullable(additionalConfig)
|
||||||
|
.map(config -> config.get("actionButtonConfig")).filter(JsonNode::isObject)
|
||||||
|
.map(config -> (ObjectNode) config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -50,4 +92,10 @@ public class WebDeliveryMethodNotificationTemplate extends DeliveryMethodNotific
|
|||||||
return new WebDeliveryMethodNotificationTemplate(this);
|
return new WebDeliveryMethodNotificationTemplate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAny(String... params) {
|
||||||
|
return super.containsAny(params) || StringUtils.containsAny(subject, params)
|
||||||
|
|| StringUtils.containsAny(getButtonText(), params) || StringUtils.containsAny(getButtonLink(), params);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,8 @@ public interface NotificationRepository extends JpaRepository<NotificationEntity
|
|||||||
|
|
||||||
@Modifying
|
@Modifying
|
||||||
@Transactional
|
@Transactional
|
||||||
@Query("UPDATE NotificationEntity n SET n.status = :status WHERE n.recipientId = :recipientId")
|
@Query("UPDATE NotificationEntity n SET n.status = :status " +
|
||||||
|
"WHERE n.recipientId = :recipientId AND n.status <> :status")
|
||||||
int updateStatusByRecipientId(@Param("recipientId") UUID recipientId,
|
int updateStatusByRecipientId(@Param("recipientId") UUID recipientId,
|
||||||
@Param("status") NotificationStatus status);
|
@Param("status") NotificationStatus status);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user