Notification rule node; refactoring
This commit is contained in:
parent
4e8e33ca1c
commit
cd50eabd40
@ -193,7 +193,7 @@ public class NotificationController extends BaseController {
|
|||||||
notificationRequest.setStatus(null);
|
notificationRequest.setStatus(null);
|
||||||
notificationRequest.setStats(null);
|
notificationRequest.setStats(null);
|
||||||
|
|
||||||
return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, notificationCenter::processNotificationRequest);
|
return doSaveAndLog(EntityType.NOTIFICATION_REQUEST, notificationRequest, (tenantId, request) -> notificationCenter.processNotificationRequest(tenantId, request, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/notification/request/preview")
|
@PostMapping("/notification/request/preview")
|
||||||
|
|||||||
@ -82,6 +82,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -105,7 +106,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
|
|||||||
private Map<NotificationDeliveryMethod, NotificationChannel> channels;
|
private Map<NotificationDeliveryMethod, NotificationChannel> channels;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest request) {
|
public NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest request, Consumer<NotificationRequestStats> callback) {
|
||||||
if (!rateLimitService.checkRateLimit(tenantId, LimitedApi.NOTIFICATION_REQUEST)) {
|
if (!rateLimitService.checkRateLimit(tenantId, LimitedApi.NOTIFICATION_REQUEST)) {
|
||||||
throw new TbRateLimitsException(EntityType.TENANT);
|
throw new TbRateLimitsException(EntityType.TENANT);
|
||||||
}
|
}
|
||||||
@ -173,6 +174,14 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[{}] Failed to update stats for notification request", requestId, e);
|
log.error("[{}] Failed to update stats for notification request", requestId, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
try {
|
||||||
|
callback.accept(stats);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to process callback for notification request {}", requestId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}, dbCallbackExecutorService);
|
}, dbCallbackExecutorService);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -110,7 +110,7 @@ public class DefaultNotificationSchedulerService extends AbstractPartitionBasedS
|
|||||||
|
|
||||||
notificationExecutor.executeAsync(() -> {
|
notificationExecutor.executeAsync(() -> {
|
||||||
try {
|
try {
|
||||||
notificationCenter.processNotificationRequest(tenantId, notificationRequest);
|
notificationCenter.processNotificationRequest(tenantId, notificationRequest, null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to process scheduled notification request {}", notificationRequest.getId(), e);
|
log.error("Failed to process scheduled notification request {}", notificationRequest.getId(), e);
|
||||||
NotificationRequestStats stats = new NotificationRequestStats();
|
NotificationRequestStats stats = new NotificationRequestStats();
|
||||||
|
|||||||
@ -19,7 +19,6 @@ 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.collections4.MapUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.thingsboard.server.common.data.id.TenantId;
|
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;
|
||||||
@ -32,12 +31,12 @@ 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 org.thingsboard.server.common.data.util.TemplateUtils;
|
||||||
|
|
||||||
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.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
||||||
|
|
||||||
@ -58,7 +57,6 @@ public class NotificationProcessingContext {
|
|||||||
@Getter
|
@Getter
|
||||||
private final NotificationRequestStats stats;
|
private final NotificationRequestStats stats;
|
||||||
|
|
||||||
private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\$\\{([a-zA-Z]+)(:[a-zA-Z]+)?}");
|
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationTemplate template, NotificationSettings settings) {
|
public NotificationProcessingContext(TenantId tenantId, NotificationRequest request, NotificationTemplate template, NotificationSettings settings) {
|
||||||
@ -109,47 +107,25 @@ public class NotificationProcessingContext {
|
|||||||
if (templateContext.isEmpty()) return template;
|
if (templateContext.isEmpty()) return template;
|
||||||
|
|
||||||
template = (T) template.copy();
|
template = (T) template.copy();
|
||||||
template.setBody(processTemplate(template.getBody(), templateContext));
|
template.setBody(TemplateUtils.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(TemplateUtils.processTemplate(subject, templateContext));
|
||||||
}
|
}
|
||||||
if (template instanceof WebDeliveryMethodNotificationTemplate) {
|
if (template instanceof WebDeliveryMethodNotificationTemplate) {
|
||||||
WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template;
|
WebDeliveryMethodNotificationTemplate webNotificationTemplate = (WebDeliveryMethodNotificationTemplate) template;
|
||||||
String buttonText = webNotificationTemplate.getButtonText();
|
String buttonText = webNotificationTemplate.getButtonText();
|
||||||
if (isNotEmpty(buttonText)) {
|
if (isNotEmpty(buttonText)) {
|
||||||
webNotificationTemplate.setButtonText(processTemplate(buttonText, templateContext));
|
webNotificationTemplate.setButtonText(TemplateUtils.processTemplate(buttonText, templateContext));
|
||||||
}
|
}
|
||||||
String buttonLink = webNotificationTemplate.getButtonLink();
|
String buttonLink = webNotificationTemplate.getButtonLink();
|
||||||
if (isNotEmpty(buttonLink)) {
|
if (isNotEmpty(buttonLink)) {
|
||||||
webNotificationTemplate.setButtonLink(processTemplate(buttonLink, templateContext));
|
webNotificationTemplate.setButtonLink(TemplateUtils.processTemplate(buttonLink, templateContext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String processTemplate(String template, Map<String, String> context) {
|
|
||||||
return TEMPLATE_PARAM_PATTERN.matcher(template).replaceAll(matchResult -> {
|
|
||||||
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 function = matchResult.group(2);
|
|
||||||
if (function != null) {
|
|
||||||
switch (function) {
|
|
||||||
case ":upperCase":
|
|
||||||
return value.toUpperCase();
|
|
||||||
case ":lowerCase":
|
|
||||||
return value.toLowerCase();
|
|
||||||
case ":capitalize":
|
|
||||||
return StringUtils.capitalize(value.toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> createTemplateContextForRecipient(NotificationRecipient recipient) {
|
private Map<String, String> createTemplateContextForRecipient(NotificationRecipient recipient) {
|
||||||
return Map.of(
|
return Map.of(
|
||||||
"recipientEmail", Strings.nullToEmpty(recipient.getEmail()),
|
"recipientEmail", Strings.nullToEmpty(recipient.getEmail()),
|
||||||
|
|||||||
@ -154,7 +154,7 @@ public class DefaultNotificationRuleProcessingService implements NotificationRul
|
|||||||
notificationExecutor.submit(() -> {
|
notificationExecutor.submit(() -> {
|
||||||
try {
|
try {
|
||||||
log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delayInSec, targets);
|
log.debug("Submitting notification request for rule '{}' with delay of {} sec to targets {}", rule.getName(), delayInSec, targets);
|
||||||
notificationCenter.processNotificationRequest(rule.getTenantId(), notificationRequest);
|
notificationCenter.processNotificationRequest(rule.getTenantId(), notificationRequest, null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to process notification request for tenant {} for rule {}", rule.getTenantId(), rule.getId(), e);
|
log.error("Failed to process notification request for tenant {} for rule {}", rule.getTenantId(), rule.getId(), e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.thingsboard.rule.engine.api.NotificationCenter;
|
import org.thingsboard.rule.engine.api.NotificationCenter;
|
||||||
import org.thingsboard.server.common.data.User;
|
import org.thingsboard.server.common.data.User;
|
||||||
import org.thingsboard.server.common.data.id.NotificationTargetId;
|
import org.thingsboard.server.common.data.id.NotificationTargetId;
|
||||||
import org.thingsboard.server.common.data.id.UserId;
|
|
||||||
import org.thingsboard.server.common.data.notification.Notification;
|
import org.thingsboard.server.common.data.notification.Notification;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationRequest;
|
import org.thingsboard.server.common.data.notification.NotificationRequest;
|
||||||
@ -333,8 +332,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
|||||||
target1Config.setUsersFilter(userListFilter);
|
target1Config.setUsersFilter(userListFilter);
|
||||||
target1.setConfiguration(target1Config);
|
target1.setConfiguration(target1Config);
|
||||||
target1 = saveNotificationTarget(target1);
|
target1 = saveNotificationTarget(target1);
|
||||||
List<UserId> recipients = new ArrayList<>();
|
List<String> recipients = new ArrayList<>();
|
||||||
recipients.add(tenantAdminUserId);
|
recipients.add(TENANT_ADMIN_EMAIL);
|
||||||
|
|
||||||
createDifferentCustomer();
|
createDifferentCustomer();
|
||||||
loginTenantAdmin();
|
loginTenantAdmin();
|
||||||
@ -346,7 +345,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
|||||||
customerUser.setCustomerId(differentCustomerId);
|
customerUser.setCustomerId(differentCustomerId);
|
||||||
customerUser.setEmail("other-customer-" + i + "@thingsboard.org");
|
customerUser.setEmail("other-customer-" + i + "@thingsboard.org");
|
||||||
customerUser = createUser(customerUser, "12345678");
|
customerUser = createUser(customerUser, "12345678");
|
||||||
recipients.add(customerUser.getId());
|
recipients.add(customerUser.getEmail());
|
||||||
}
|
}
|
||||||
NotificationTarget target2 = new NotificationTarget();
|
NotificationTarget target2 = new NotificationTarget();
|
||||||
target2.setName("Other customer users");
|
target2.setName("Other customer users");
|
||||||
@ -402,7 +401,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
|||||||
assertThat(preview.getRecipientsCountByTarget().get(target1.getName())).isEqualTo(1);
|
assertThat(preview.getRecipientsCountByTarget().get(target1.getName())).isEqualTo(1);
|
||||||
assertThat(preview.getRecipientsCountByTarget().get(target2.getName())).isEqualTo(customerUsersCount);
|
assertThat(preview.getRecipientsCountByTarget().get(target2.getName())).isEqualTo(customerUsersCount);
|
||||||
assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount);
|
assertThat(preview.getTotalRecipientsCount()).isEqualTo(1 + customerUsersCount);
|
||||||
assertThat(preview.getRecipientsPreview()).extracting(User::getId).containsAll(recipients);
|
assertThat(preview.getRecipientsPreview()).containsAll(recipients);
|
||||||
|
|
||||||
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = preview.getProcessedTemplates();
|
Map<NotificationDeliveryMethod, DeliveryMethodNotificationTemplate> processedTemplates = preview.getProcessedTemplates();
|
||||||
assertThat(processedTemplates.get(NotificationDeliveryMethod.WEB)).asInstanceOf(type(WebDeliveryMethodNotificationTemplate.class))
|
assertThat(processedTemplates.get(NotificationDeliveryMethod.WEB)).asInstanceOf(type(WebDeliveryMethodNotificationTemplate.class))
|
||||||
@ -485,7 +484,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
|
|||||||
NotificationTemplateConfig config = new NotificationTemplateConfig();
|
NotificationTemplateConfig config = new NotificationTemplateConfig();
|
||||||
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
|
SlackDeliveryMethodNotificationTemplate slackNotificationTemplate = new SlackDeliveryMethodNotificationTemplate();
|
||||||
slackNotificationTemplate.setEnabled(true);
|
slackNotificationTemplate.setEnabled(true);
|
||||||
slackNotificationTemplate.setBody("To Slack :) ${recipientEmail}");
|
slackNotificationTemplate.setBody("To Slack :)");
|
||||||
config.setDeliveryMethodsTemplates(Map.of(
|
config.setDeliveryMethodsTemplates(Map.of(
|
||||||
NotificationDeliveryMethod.SLACK, slackNotificationTemplate
|
NotificationDeliveryMethod.SLACK, slackNotificationTemplate
|
||||||
));
|
));
|
||||||
|
|||||||
@ -26,6 +26,7 @@ public enum NotificationType {
|
|||||||
ALARM_ASSIGNMENT,
|
ALARM_ASSIGNMENT,
|
||||||
NEW_PLATFORM_VERSION,
|
NEW_PLATFORM_VERSION,
|
||||||
ENTITIES_LIMIT,
|
ENTITIES_LIMIT,
|
||||||
API_USAGE_LIMIT
|
API_USAGE_LIMIT,
|
||||||
|
RULE_ENGINE
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,10 +33,13 @@ public class RuleEngineOriginatedNotificationInfo implements NotificationInfo {
|
|||||||
private EntityId msgOriginator;
|
private EntityId msgOriginator;
|
||||||
private String msgType;
|
private String msgType;
|
||||||
private Map<String, String> msgMetadata;
|
private Map<String, String> msgMetadata;
|
||||||
|
private Map<String, String> msgData;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> getTemplateData() {
|
public Map<String, String> getTemplateData() {
|
||||||
Map<String, String> templateData = new HashMap<>(msgMetadata);
|
Map<String, String> templateData = new HashMap<>();
|
||||||
|
templateData.putAll(msgMetadata);
|
||||||
|
templateData.putAll(msgData);
|
||||||
templateData.put("originatorType", msgOriginator.getEntityType().getNormalName());
|
templateData.put("originatorType", msgOriginator.getEntityType().getNormalName());
|
||||||
templateData.put("originatorId", msgOriginator.getId().toString());
|
templateData.put("originatorId", msgOriginator.getId().toString());
|
||||||
templateData.put("msgType", msgType);
|
templateData.put("msgType", msgType);
|
||||||
|
|||||||
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2023 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.
|
||||||
|
*/
|
||||||
|
package org.thingsboard.server.common.data.util;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static com.google.common.base.Strings.nullToEmpty;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.removeStart;
|
||||||
|
|
||||||
|
public class TemplateUtils {
|
||||||
|
|
||||||
|
private static final Pattern TEMPLATE_PARAM_PATTERN = Pattern.compile("\\$\\{(.+?)(:[a-zA-Z]+)?}");
|
||||||
|
|
||||||
|
private static final Map<String, UnaryOperator<String>> FUNCTIONS = Map.of(
|
||||||
|
"upperCase", String::toUpperCase,
|
||||||
|
"lowerCase", String::toLowerCase,
|
||||||
|
"capitalize", StringUtils::capitalize
|
||||||
|
);
|
||||||
|
|
||||||
|
private TemplateUtils() {}
|
||||||
|
|
||||||
|
public static String processTemplate(String template, Map<String, String> context) {
|
||||||
|
return TEMPLATE_PARAM_PATTERN.matcher(template).replaceAll(matchResult -> {
|
||||||
|
String key = matchResult.group(1);
|
||||||
|
if (!context.containsKey(key)) {
|
||||||
|
return "\\" + matchResult.group();
|
||||||
|
}
|
||||||
|
String value = nullToEmpty(context.get(key));
|
||||||
|
String function = removeStart(matchResult.group(2), ":");
|
||||||
|
if (function != null) {
|
||||||
|
if (FUNCTIONS.containsKey(function)) {
|
||||||
|
value = FUNCTIONS.get(function).apply(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -32,7 +32,10 @@ import org.thingsboard.server.common.data.kv.KvEntry;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
@ -237,6 +240,25 @@ public class JacksonUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> toFlatMap(JsonNode node) {
|
||||||
|
HashMap<String, String> map = new HashMap<>();
|
||||||
|
toFlatMap(node, "", map);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void toFlatMap(JsonNode node, String currentPath, Map<String, String> map) {
|
||||||
|
if (node.isObject()) {
|
||||||
|
Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
|
||||||
|
currentPath = currentPath.isEmpty() ? "" : currentPath + ".";
|
||||||
|
while (fields.hasNext()) {
|
||||||
|
Map.Entry<String, JsonNode> entry = fields.next();
|
||||||
|
toFlatMap(entry.getValue(), currentPath + entry.getKey(), map);
|
||||||
|
}
|
||||||
|
} else if (node.isValueNode()) {
|
||||||
|
map.put(currentPath, node.asText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) {
|
public static void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) {
|
||||||
addKvEntry(entityNode, kvEntry, kvEntry.getKey());
|
addKvEntry(entityNode, kvEntry, kvEntry.getKey());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,12 +21,14 @@ import org.thingsboard.server.common.data.id.TenantId;
|
|||||||
import org.thingsboard.server.common.data.id.UserId;
|
import org.thingsboard.server.common.data.id.UserId;
|
||||||
import org.thingsboard.server.common.data.notification.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 java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public interface NotificationCenter {
|
public interface NotificationCenter {
|
||||||
|
|
||||||
NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest);
|
NotificationRequest processNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest, Consumer<NotificationRequestStats> callback);
|
||||||
|
|
||||||
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId);
|
void deleteNotificationRequest(TenantId tenantId, NotificationRequestId notificationRequestId);
|
||||||
|
|
||||||
|
|||||||
@ -16,12 +16,14 @@
|
|||||||
package org.thingsboard.rule.engine.notification;
|
package org.thingsboard.rule.engine.notification;
|
||||||
|
|
||||||
import org.thingsboard.common.util.DonAsynchron;
|
import org.thingsboard.common.util.DonAsynchron;
|
||||||
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
import org.thingsboard.rule.engine.api.RuleNode;
|
import org.thingsboard.rule.engine.api.RuleNode;
|
||||||
import org.thingsboard.rule.engine.api.TbContext;
|
import org.thingsboard.rule.engine.api.TbContext;
|
||||||
import org.thingsboard.rule.engine.api.TbNode;
|
import org.thingsboard.rule.engine.api.TbNode;
|
||||||
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
|
||||||
import org.thingsboard.rule.engine.api.TbNodeException;
|
import org.thingsboard.rule.engine.api.TbNodeException;
|
||||||
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
|
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
|
||||||
|
import org.thingsboard.server.common.data.id.NotificationTemplateId;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationRequest;
|
import org.thingsboard.server.common.data.notification.NotificationRequest;
|
||||||
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
|
import org.thingsboard.server.common.data.notification.NotificationRequestConfig;
|
||||||
import org.thingsboard.server.common.data.notification.info.RuleEngineOriginatedNotificationInfo;
|
import org.thingsboard.server.common.data.notification.info.RuleEngineOriginatedNotificationInfo;
|
||||||
@ -36,7 +38,7 @@ import java.util.concurrent.ExecutionException;
|
|||||||
name = "send notification",
|
name = "send notification",
|
||||||
configClazz = TbNotificationNodeConfiguration.class,
|
configClazz = TbNotificationNodeConfiguration.class,
|
||||||
nodeDescription = "Sends notification to targets using the template",
|
nodeDescription = "Sends notification to targets using the template",
|
||||||
nodeDetails = "Will send notification to the specified targets",
|
nodeDetails = "Will send notification to the specified targets using the template",
|
||||||
uiResources = {"static/rulenode/rulenode-core-config.js"}
|
uiResources = {"static/rulenode/rulenode-core-config.js"}
|
||||||
)
|
)
|
||||||
public class TbNotificationNode implements TbNode {
|
public class TbNotificationNode implements TbNode {
|
||||||
@ -53,29 +55,28 @@ public class TbNotificationNode implements TbNode {
|
|||||||
RuleEngineOriginatedNotificationInfo notificationInfo = RuleEngineOriginatedNotificationInfo.builder()
|
RuleEngineOriginatedNotificationInfo notificationInfo = RuleEngineOriginatedNotificationInfo.builder()
|
||||||
.msgOriginator(msg.getOriginator())
|
.msgOriginator(msg.getOriginator())
|
||||||
.msgMetadata(msg.getMetaData().getData())
|
.msgMetadata(msg.getMetaData().getData())
|
||||||
|
.msgData(JacksonUtil.toFlatMap(JacksonUtil.toJsonNode(msg.getData())))
|
||||||
.msgType(msg.getType())
|
.msgType(msg.getType())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
NotificationRequest notificationRequest = NotificationRequest.builder()
|
NotificationRequest notificationRequest = NotificationRequest.builder()
|
||||||
.tenantId(ctx.getTenantId())
|
.tenantId(ctx.getTenantId())
|
||||||
.targets(config.getTargets())
|
.targets(config.getTargets())
|
||||||
.templateId(config.getTemplateId())
|
.templateId(new NotificationTemplateId(config.getTemplateId()))
|
||||||
.info(notificationInfo)
|
.info(notificationInfo)
|
||||||
.additionalConfig(new NotificationRequestConfig())
|
.additionalConfig(new NotificationRequestConfig())
|
||||||
.originatorEntityId(ctx.getSelf().getRuleChainId())
|
.originatorEntityId(ctx.getSelf().getRuleChainId())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
DonAsynchron.withCallback(ctx.getNotificationExecutor().executeAsync(() -> {
|
DonAsynchron.withCallback(ctx.getNotificationExecutor().executeAsync(() -> {
|
||||||
return ctx.getNotificationCenter().processNotificationRequest(ctx.getTenantId(), notificationRequest);
|
return ctx.getNotificationCenter().processNotificationRequest(ctx.getTenantId(), notificationRequest, stats -> {
|
||||||
|
TbMsgMetaData metaData = msg.getMetaData().copy();
|
||||||
|
metaData.putValue("notificationRequestResult", JacksonUtil.toString(stats));
|
||||||
|
ctx.tellSuccess(TbMsg.transformMsg(msg, metaData));
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
r -> {
|
r -> {},
|
||||||
TbMsgMetaData msgMetaData = msg.getMetaData().copy();
|
e -> ctx.tellFailure(msg, e));
|
||||||
msgMetaData.putValue("notificationRequestId", r.getUuidId().toString());
|
|
||||||
ctx.tellSuccess(TbMsg.transformMsg(msg, msgMetaData));
|
|
||||||
},
|
|
||||||
e -> {
|
|
||||||
ctx.tellFailure(msg, e);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,14 +32,11 @@ public class TbNotificationNodeConfiguration implements NodeConfiguration<TbNoti
|
|||||||
@NotEmpty
|
@NotEmpty
|
||||||
private List<UUID> targets;
|
private List<UUID> targets;
|
||||||
@NotNull
|
@NotNull
|
||||||
private NotificationTemplateId templateId;
|
private UUID templateId;
|
||||||
private NotificationRequestConfig additionalConfig;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TbNotificationNodeConfiguration defaultConfiguration() {
|
public TbNotificationNodeConfiguration defaultConfiguration() {
|
||||||
TbNotificationNodeConfiguration config = new TbNotificationNodeConfiguration();
|
return new TbNotificationNodeConfiguration();
|
||||||
config.setAdditionalConfig(new NotificationRequestConfig());
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -441,7 +441,8 @@ export enum NotificationType {
|
|||||||
ALARM_ASSIGNMENT = 'ALARM_ASSIGNMENT',
|
ALARM_ASSIGNMENT = 'ALARM_ASSIGNMENT',
|
||||||
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT',
|
RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT = 'RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT',
|
||||||
ENTITIES_LIMIT = 'ENTITIES_LIMIT',
|
ENTITIES_LIMIT = 'ENTITIES_LIMIT',
|
||||||
API_USAGE_LIMIT = 'API_USAGE_LIMIT'
|
API_USAGE_LIMIT = 'API_USAGE_LIMIT',
|
||||||
|
RULE_ENGINE = 'RULE_ENGINE'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotificationTypeIcons = new Map<NotificationType, string | null>([
|
export const NotificationTypeIcons = new Map<NotificationType, string | null>([
|
||||||
@ -527,12 +528,20 @@ export const NotificationTemplateTypeTranslateMap = new Map<NotificationType, No
|
|||||||
{
|
{
|
||||||
name: 'notification.template-type.entities-limit',
|
name: 'notification.template-type.entities-limit',
|
||||||
helpId: 'notification/entities_limit'
|
helpId: 'notification/entities_limit'
|
||||||
}],
|
}
|
||||||
|
],
|
||||||
[NotificationType.API_USAGE_LIMIT,
|
[NotificationType.API_USAGE_LIMIT,
|
||||||
{
|
{
|
||||||
name: 'notification.template-type.api-usage-limit',
|
name: 'notification.template-type.api-usage-limit',
|
||||||
helpId: 'notification/api_usage_limit'
|
helpId: 'notification/api_usage_limit'
|
||||||
}]
|
}
|
||||||
|
],
|
||||||
|
[NotificationType.RULE_ENGINE,
|
||||||
|
{
|
||||||
|
name: 'notification.template-type.rule-engine',
|
||||||
|
helpId: 'notification/rule-engine'
|
||||||
|
}
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export enum TriggerType {
|
export enum TriggerType {
|
||||||
|
|||||||
54
ui-ngx/src/assets/help/en_US/notification/rule-engine.md
Normal file
54
ui-ngx/src/assets/help/en_US/notification/rule-engine.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#### Rule engine notification templatization
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type.
|
||||||
|
See the available types and parameters below:
|
||||||
|
|
||||||
|
Available template parameters:
|
||||||
|
|
||||||
|
* values from the incoming message metadata;
|
||||||
|
* values from the incoming message data;
|
||||||
|
* *originatorType* - type of the originator, e.g. 'Device';
|
||||||
|
* *originatorId* - id of the originator
|
||||||
|
* *msgType* - type of the message
|
||||||
|
* *recipientEmail* - email of the recipient;
|
||||||
|
* *recipientFirstName* - first name of the recipient;
|
||||||
|
* *recipientLastName* - last name of the recipient;
|
||||||
|
|
||||||
|
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`.
|
||||||
|
You may also modify the value of the parameter with one of the suffixes:
|
||||||
|
|
||||||
|
* `upperCase`, for example - `${recipientFirstName:upperCase}`
|
||||||
|
* `lowerCase`, for example - `${recipientFirstName:lowerCase}`
|
||||||
|
* `capitalize`, for example - `${recipientFirstName:capitalize}`
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
Let's assume the incoming message to Rule node has the following data:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"building_1": {
|
||||||
|
"temperature": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The following template:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Building 1: temperature is ${building_1.temperature}
|
||||||
|
{:copy-code}
|
||||||
|
```
|
||||||
|
|
||||||
|
will be transformed to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Building 1: temperature is 24
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
@ -2931,7 +2931,8 @@
|
|||||||
"entities-limit": "Entities limit",
|
"entities-limit": "Entities limit",
|
||||||
"entity-action": "Entity action",
|
"entity-action": "Entity action",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"rule-engine-lifecycle-event": "Rule engine lifecycle event"
|
"rule-engine-lifecycle-event": "Rule engine lifecycle event",
|
||||||
|
"rule-engine": "Rule engine"
|
||||||
},
|
},
|
||||||
"templates": "Templates",
|
"templates": "Templates",
|
||||||
"tenant-profiles-list-rule-hint": "If the field is empty, the trigger will be applied to all tenant profiles",
|
"tenant-profiles-list-rule-hint": "If the field is empty, the trigger will be applied to all tenant profiles",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user