diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 23aaba0e48..c3e9215d90 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -47,11 +47,13 @@ import org.thingsboard.server.common.data.notification.targets.NotificationRecip 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.platform.PlatformUsersNotificationTargetConfig; +import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation; import org.thingsboard.server.common.data.notification.targets.slack.SlackNotificationTargetConfig; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationSettingsService; @@ -230,7 +232,8 @@ public class NotificationController extends BaseController { NotificationTargetType targetType = target.getConfiguration().getType(); if (targetType == NotificationTargetType.PLATFORM_USERS) { PageData recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), - (PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize)); + (PlatformUsersNotificationTargetConfig) target.getConfiguration(), new PageLink(recipientsPreviewSize, 0, null, + new SortOrder("createdTime", SortOrder.Direction.DESC))); recipientsCount = (int) recipients.getTotalElements(); recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList()); } else { @@ -240,7 +243,15 @@ public class NotificationController extends BaseController { firstRecipient.putIfAbsent(targetType, !recipientsPart.isEmpty() ? recipientsPart.get(0) : null); for (NotificationRecipient recipient : recipientsPart) { if (recipientsPreview.size() < recipientsPreviewSize) { - recipientsPreview.add(recipient.getTitle()); + String title = recipient.getTitle(); + if (recipient instanceof SlackConversation) { + title = ((SlackConversation) recipient).getPointer() + title; + } else if (recipient instanceof User) { + if (!title.equals(recipient.getEmail())) { + title += " (" + recipient.getEmail() + ")"; + } + } + recipientsPreview.add(title); } else { break; } diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index 995ca30212..05e78e4060 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -42,6 +42,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.notification.trigger.EntitiesLimitTrigger; +import org.thingsboard.server.common.msg.notification.trigger.EntityActionTrigger; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.queue.notification.NotificationRuleProcessor; @@ -190,12 +191,6 @@ public class EntityActionService { AlarmComment comment = extractParameter(AlarmComment.class, 0, additionalInfo); metaData.putValue("comment", json.writeValueAsString(comment)); } - if (actionType == ActionType.ADDED && !tenantId.isSysTenantId()) { - notificationRuleProcessor.process(EntitiesLimitTrigger.builder() - .tenantId(tenantId) - .entityType(entityId.getEntityType()) - .build()); - } ObjectNode entityNode; if (entity != null) { entityNode = json.valueToTree(entity); @@ -242,12 +237,28 @@ public class EntityActionService { entityNode = json.valueToTree(extractParameter(EntityRelation.class, 0, additionalInfo)); } } - TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); + if (tenantId == null || tenantId.isNullUid()) { if (entity instanceof HasTenantId) { tenantId = ((HasTenantId) entity).getTenantId(); } } + if (tenantId != null && !tenantId.isSysTenantId()) { + if (actionType == ActionType.ADDED) { + notificationRuleProcessor.process(EntitiesLimitTrigger.builder() + .tenantId(tenantId) + .entityType(entityId.getEntityType()) + .build()); + } + notificationRuleProcessor.process(EntityActionTrigger.builder() + .tenantId(tenantId) + .entityId(entityId) + .entity(entity) + .actionType(actionType) + .user(user) + .build()); + } + TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); } catch (Exception e) { log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 55eae83af4..f3fd18141a 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -364,6 +364,11 @@ public class DefaultMailService implements MailService { mailSender.testConnection(); } + @Override + public boolean isConfigured(TenantId tenantId) { + return mailSender != null; + } + private String toEnabledValueLabel(ApiFeature apiFeature) { switch (apiFeature) { case DB: diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java index 25ff1dd50e..27a9cabe43 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java @@ -128,6 +128,7 @@ public class NotificationProcessingContext { private Map createTemplateContextForRecipient(NotificationRecipient recipient) { return Map.of( + "recipientTitle", recipient.getTitle(), "recipientEmail", Strings.nullToEmpty(recipient.getEmail()), "recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()), "recipientLastName", Strings.nullToEmpty(recipient.getLastName()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java index f5bbc57954..8b3c9551c2 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java @@ -49,10 +49,8 @@ public class EmailNotificationChannel implements NotificationChannel { @@ -43,8 +44,8 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio return false; } Alarm alarm = JacksonUtil.fromString(trigger.getMsg().getData(), Alarm.class); - return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && - (isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) && + return emptyOrContains(triggerConfig.getAlarmTypes(), alarm.getType()) && + emptyOrContains(triggerConfig.getAlarmSeverities(), alarm.getSeverity()) && (isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm)); } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java index f10e395a78..cf71718f83 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigg import java.util.Set; import static org.apache.commons.collections.CollectionUtils.isEmpty; +import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains; @Service public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor { @@ -53,8 +54,8 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu } } Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class); - return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && - (isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) && + return emptyOrContains(triggerConfig.getAlarmTypes(), alarm.getType()) && + emptyOrContains(triggerConfig.getAlarmSeverities(), alarm.getSeverity()) && (isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm)); } @@ -66,9 +67,9 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu return AlarmCommentNotificationInfo.builder() .comment(comment.getComment().get("text").asText()) .action(msg.getType().equals(DataConstants.COMMENT_CREATED) ? "added" : "updated") - .userEmail(trigger.getMsg().getMetaData().getValue("userEmail")) - .userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName")) - .userLastName(trigger.getMsg().getMetaData().getValue("userLastName")) + .userEmail(msg.getMetaData().getValue("userEmail")) + .userFirstName(msg.getMetaData().getValue("userFirstName")) + .userLastName(msg.getMetaData().getValue("userLastName")) .alarmId(alarmInfo.getUuidId()) .alarmType(alarmInfo.getType()) .alarmOriginator(alarmInfo.getOriginator()) diff --git a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java index c8ebf73304..c32ed26854 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.notification.rule.trigger; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; @@ -25,11 +26,10 @@ import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotific import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction; import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; -import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger; -import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.apache.commons.collections.CollectionUtils.isNotEmpty; +import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains; @Service public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor { @@ -85,11 +85,11 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor { +public class EntityActionTriggerProcessor implements NotificationRuleTriggerProcessor { @Override - public boolean matchesFilter(RuleEngineMsgTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) { - String msgType = trigger.getMsg().getType(); - if (msgType.equals(DataConstants.ENTITY_CREATED)) { - if (!triggerConfig.isCreated()) { - return false; - } - } else if (msgType.equals(DataConstants.ENTITY_UPDATED)) { - if (!triggerConfig.isUpdated()) { - return false; - } - } else if (msgType.equals(DataConstants.ENTITY_DELETED)) { - if (!triggerConfig.isDeleted()) { - return false; - } - } else { - return false; - } - return isEmpty(triggerConfig.getEntityTypes()) || triggerConfig.getEntityTypes().contains(getEntityType(trigger.getMsg())); + public boolean matchesFilter(EntityActionTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) { + return ((trigger.getActionType() == ActionType.ADDED && triggerConfig.isCreated()) + || (trigger.getActionType() == ActionType.UPDATED && triggerConfig.isUpdated()) + || (trigger.getActionType() == ActionType.DELETED && triggerConfig.isDeleted())) + && emptyOrContains(triggerConfig.getEntityTypes(), trigger.getEntityId().getEntityType()); } @Override - public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) { - TbMsg msg = trigger.getMsg(); - String msgType = msg.getType(); - ActionType actionType = msgType.equals(DataConstants.ENTITY_CREATED) ? ActionType.ADDED : - msgType.equals(DataConstants.ENTITY_UPDATED) ? ActionType.UPDATED : - msgType.equals(DataConstants.ENTITY_DELETED) ? ActionType.DELETED : null; + public RuleOriginatedNotificationInfo constructNotificationInfo(EntityActionTrigger trigger) { return EntityActionNotificationInfo.builder() - .entityId(msg.getOriginator()) - .entityName(msg.getMetaData().getValue("entityName")) - .actionType(actionType) - .userId(UUID.fromString(msg.getMetaData().getValue("userId"))) - .userEmail(trigger.getMsg().getMetaData().getValue("userEmail")) - .userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName")) - .userLastName(trigger.getMsg().getMetaData().getValue("userLastName")) - .entityCustomerId(msg.getCustomerId()) + .entityId(trigger.getEntityId()) + .entityName(trigger.getEntity().getName()) + .actionType(trigger.getActionType()) + .userId(trigger.getUser().getUuidId()) + .userTitle(trigger.getUser().getTitle()) + .userEmail(trigger.getUser().getEmail()) + .userFirstName(trigger.getUser().getFirstName()) + .userLastName(trigger.getUser().getLastName()) + .entityCustomerId(trigger.getEntity() instanceof HasCustomerId ? + ((HasCustomerId) trigger.getEntity()).getCustomerId() : + trigger.getUser().getCustomerId()) .build(); } - private static EntityType getEntityType(TbMsg msg) { - return Optional.ofNullable(msg.getMetaData().getValue("entityType")) - .map(EntityType::valueOf).orElse(null); - } - @Override public NotificationRuleTriggerType getTriggerType() { return NotificationRuleTriggerType.ENTITY_ACTION; } - @Override - public Set getSupportedMsgTypes() { - return Set.of(DataConstants.ENTITY_CREATED, DataConstants.ENTITY_UPDATED, DataConstants.ENTITY_DELETED); - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java index 4c4f76c2b9..400586235e 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java @@ -18,13 +18,13 @@ package org.thingsboard.server.service.queue; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.exception.TenantNotFoundException; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @Slf4j @Service @@ -46,7 +46,7 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService if (tenantProfile != null) { return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbRuleEngine()); } else { - throw new RuntimeException("Tenant not found!"); + throw new TenantNotFoundException(tenantId); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java index a36b12f0fc..ee54a02620 100644 --- a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java +++ b/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java @@ -28,7 +28,6 @@ import com.slack.api.methods.response.conversations.ConversationsListResponse; import com.slack.api.methods.response.users.UsersListResponse; import com.slack.api.model.ConversationType; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.server.common.data.id.TenantId; @@ -44,8 +43,6 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.isNotEmpty; - @Service @RequiredArgsConstructor public class DefaultSlackService implements SlackService { @@ -81,15 +78,11 @@ public class DefaultSlackService implements SlackService { .filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot()) .map(user -> { SlackConversation conversation = new SlackConversation(); + conversation.setType(conversationType); conversation.setId(user.getId()); - conversation.setShortName(user.getName()); + conversation.setName(user.getName()); conversation.setWholeName(user.getProfile() != null ? user.getProfile().getRealNameNormalized() : user.getRealName()); conversation.setEmail(user.getProfile() != null ? user.getProfile().getEmail() : null); - String title = "@" + conversation.getShortName(); - if (isNotEmpty(conversation.getWholeName()) && !conversation.getWholeName().equals(conversation.getShortName())) { - title += " (" + conversation.getWholeName() + ")"; - } - conversation.setTitle(title); return conversation; }) .collect(Collectors.toList()); @@ -107,10 +100,10 @@ public class DefaultSlackService implements SlackService { .filter(channel -> !channel.isArchived()) .map(channel -> { SlackConversation conversation = new SlackConversation(); + conversation.setType(conversationType); conversation.setId(channel.getId()); - conversation.setShortName(channel.getName()); + conversation.setName(channel.getName()); conversation.setWholeName(channel.getNameNormalized()); - conversation.setTitle("#" + channel.getName()); return conversation; }) .collect(Collectors.toList()); @@ -118,14 +111,6 @@ public class DefaultSlackService implements SlackService { }); } - @Override - public SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern) { - List conversations = listConversations(tenantId, token, conversationType); - return conversations.stream() - .filter(conversation -> StringUtils.containsIgnoreCase(conversation.getTitle(), namePattern)) - .findFirst().orElse(null); - } - @Override public String getToken(TenantId tenantId) { NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 005cc6ac4c..cb08e855c2 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.exception.TenantNotFoundException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -453,6 +454,9 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService tenantDevicesActivity = devicesActivity.computeIfAbsent(stateData.getTenantId(), tenantId -> Pair.of(new AtomicInteger(), new AtomicInteger())); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index c26e037b65..b5238d675b 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -35,6 +35,10 @@ import org.thingsboard.server.common.data.notification.NotificationRequestConfig import org.thingsboard.server.common.data.notification.NotificationRequestInfo; import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationType; +import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig; +import org.thingsboard.server.common.data.notification.rule.NotificationRule; +import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; @@ -52,6 +56,7 @@ import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.DaoUtil; import java.net.URISyntaxException; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; @@ -195,6 +200,32 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest new PageLink(limit, 0), unreadOnly).getData(); } + protected NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) { + NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, NotificationDeliveryMethod.WEB); + + NotificationRule rule = new NotificationRule(); + rule.setName(triggerConfig.getTriggerType() + " [" + Arrays.toString(targets) + "]"); + rule.setTemplateId(template.getId()); + rule.setTriggerType(triggerConfig.getTriggerType()); + rule.setTriggerConfig(triggerConfig); + + DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); + recipientsConfig.setTriggerType(triggerConfig.getTriggerType()); + recipientsConfig.setTargets(DaoUtil.toUUIDs(List.of(targets))); + rule.setRecipientsConfig(recipientsConfig); + + return saveNotificationRule(rule); + } + + protected NotificationRule saveNotificationRule(NotificationRule notificationRule) { + return doPost("/api/notification/rule", notificationRule, NotificationRule.class); + } + + protected PageData findNotificationRules() throws Exception { + PageLink pageLink = new PageLink(10); + return doGetTypedWithPageLink("/api/notification/rules?", new TypeReference>() {}, pageLink); + } + @Override protected NotificationApiWsClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { NotificationApiWsClient wsClient = new NotificationApiWsClient(WS_URL + wsPort, token); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index f6a296ab7e..851550eee7 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -362,13 +362,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest { SlackNotificationTargetConfig slackTargetConfig = new SlackNotificationTargetConfig(); slackTargetConfig.setConversationType(SlackConversationType.DIRECT); SlackConversation slackConversation = new SlackConversation(); + slackConversation.setType(SlackConversationType.DIRECT); slackConversation.setId("U1234567"); - slackConversation.setTitle("@jdoe (John Doe)"); + slackConversation.setName("jdoe"); slackConversation.setWholeName("John Doe"); slackTargetConfig.setConversation(slackConversation); slackTarget.setConfiguration(slackTargetConfig); slackTarget = saveNotificationTarget(slackTarget); - recipients.add(slackConversation.getTitle()); + recipients.add("@" + slackConversation.getWholeName()); NotificationTemplate notificationTemplate = new NotificationTemplate(); notificationTemplate.setNotificationType(NotificationType.GENERAL); @@ -500,8 +501,9 @@ public class NotificationApiTest extends AbstractNotificationApiTest { notificationTarget.setName(conversationName + " in Slack"); SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig(); targetConfig.setConversation(SlackConversation.builder() + .type(SlackConversationType.DIRECT) .id(conversationId) - .title(conversationName) + .name(conversationName) .build()); notificationTarget.setConfiguration(targetConfig); notificationTarget = saveNotificationTarget(notificationTarget); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index 6b21809874..998f00f8e2 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.notification; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.BooleanNode; import org.junit.Before; @@ -43,7 +42,6 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; -import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; @@ -59,7 +57,6 @@ import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotific import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction; import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; -import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.template.NotificationTemplate; @@ -73,7 +70,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; -import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -84,7 +80,6 @@ import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileServ import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -436,7 +431,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { String name = "device " + i; createDevice(name, name); } - await().atMost(5, TimeUnit.SECONDS) + await().atMost(30, TimeUnit.SECONDS) .untilAsserted(() -> { assertThat(getMyNotifications(false, 100)).size().isEqualTo(notificationRequestsLimit); }); @@ -474,21 +469,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { }, (notification, r) -> check.accept(notification)); } - private NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) { - NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, NotificationDeliveryMethod.WEB); - - NotificationRule rule = new NotificationRule(); - rule.setName(triggerConfig.getTriggerType() + " [" + Arrays.toString(targets) + "]"); - rule.setTemplateId(template.getId()); - rule.setTriggerType(triggerConfig.getTriggerType()); - rule.setTriggerConfig(triggerConfig); - - DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); - recipientsConfig.setTriggerType(triggerConfig.getTriggerType()); - recipientsConfig.setTargets(DaoUtil.toUUIDs(List.of(targets))); - rule.setRecipientsConfig(recipientsConfig); - - return saveNotificationRule(rule); + private PageData findNotificationRequests(EntityType originatorType) { + return notificationRequestService.findNotificationRequestsInfosByTenantIdAndOriginatorType(tenantId, originatorType, new PageLink(100)); } private DeviceProfile createDeviceProfileWithAlarmRules(String alarmType) { @@ -526,17 +508,4 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { return deviceProfile; } - private NotificationRule saveNotificationRule(NotificationRule notificationRule) { - return doPost("/api/notification/rule", notificationRule, NotificationRule.class); - } - - private PageData findNotificationRules() throws Exception { - PageLink pageLink = new PageLink(10); - return doGetTypedWithPageLink("/api/notification/rules?", new TypeReference>() {}, pageLink); - } - - private PageData findNotificationRequests(EntityType originatorType) { - return notificationRequestService.findNotificationRequestsInfosByTenantIdAndOriginatorType(tenantId, originatorType, new PageLink(100)); - } - } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java index 61e0531a39..480bbf939b 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java @@ -23,6 +23,8 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultMatcher; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; 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; @@ -30,7 +32,6 @@ import org.thingsboard.server.common.data.notification.targets.platform.Platform import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.notification.NotificationTargetDao; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -41,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DaoSqlTest -public class NotificationTargetApiTest extends AbstractControllerTest { +public class NotificationTargetApiTest extends AbstractNotificationApiTest { @Autowired private NotificationTargetDao notificationTargetDao; @@ -148,6 +149,26 @@ public class NotificationTargetApiTest extends AbstractControllerTest { assertThat(notificationTargetDao.findByTenantIdAndPageLink(differentTenantId, new PageLink(10)).getData()).isEmpty(); } + @Test + public void whenDeletingTargetUsedByRule_thenReturnError() throws Exception { + NotificationTarget target = createNotificationTarget(tenantAdminUserId); + createNotificationRule(new EntityActionNotificationRuleTriggerConfig(), "Test", "Test", target.getId()); + + String error = getErrorMessage(doDelete("/api/notification/target/" + target.getId()) + .andExpect(status().isBadRequest())); + assertThat(error).containsIgnoringCase("used in notification rule"); + } + + @Test + public void whenDeletingTargetUsedByScheduledNotificationRequest_thenReturnError() throws Exception { + NotificationTarget target = createNotificationTarget(tenantAdminUserId); + submitNotificationRequest(target.getId(), "Test", 100, NotificationDeliveryMethod.WEB); + + String error = getErrorMessage(doDelete("/api/notification/target/" + target.getId()) + .andExpect(status().isBadRequest())); + assertThat(error).containsIgnoringCase("referenced by scheduled notification request"); + } + private String saveAndGetError(NotificationTarget notificationTarget, ResultMatcher statusMatcher) throws Exception { return getErrorMessage(save(notificationTarget, statusMatcher)); } diff --git a/application/src/test/java/org/thingsboard/server/service/stats/DevicesStatisticsTest.java b/application/src/test/java/org/thingsboard/server/service/stats/DevicesStatisticsTest.java index d31d6639ac..226c5941d4 100644 --- a/application/src/test/java/org/thingsboard/server/service/stats/DevicesStatisticsTest.java +++ b/application/src/test/java/org/thingsboard/server/service/stats/DevicesStatisticsTest.java @@ -28,6 +28,7 @@ import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; +import org.thingsboard.server.service.state.DeviceStateService; import java.util.ArrayList; import java.util.List; @@ -35,17 +36,14 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DaoSqlTest @TestPropertySource(properties = { "usage.stats.report.enabled=true", - "transport.http.enabled=true", "usage.stats.report.interval=2", "usage.stats.gauge_report_interval=1", "state.defaultStateCheckIntervalInSec=3", "state.defaultInactivityTimeoutInSec=10" - }) public class DevicesStatisticsTest extends AbstractControllerTest { @@ -53,6 +51,8 @@ public class DevicesStatisticsTest extends AbstractControllerTest { private TbApiUsageStateService apiUsageStateService; @Autowired private TimeseriesService timeseriesService; + @Autowired + private DeviceStateService deviceStateService; private ApiUsageStateId apiUsageStateId; @@ -86,7 +86,7 @@ public class DevicesStatisticsTest extends AbstractControllerTest { }); for (Device device : activeDevices) { - postTelemetry(device.getName(), "{\"dp\":1}"); + deviceStateService.onDeviceActivity(tenantId, device.getId(), System.currentTimeMillis()); } await().atMost(40, TimeUnit.SECONDS) @@ -102,9 +102,4 @@ public class DevicesStatisticsTest extends AbstractControllerTest { .get().stream().findFirst().flatMap(KvEntry::getLongValue).orElse(null); } - @SneakyThrows - private void postTelemetry(String accessToken, String json) { - doPost("/api/v1/" + accessToken + "/telemetry", json, new String[0]).andExpect(status().isOk()); - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index 4dc71e8c44..f629151d18 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -74,7 +74,7 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H @ApiModelProperty(position = 1, value = "JSON object with the User Id. " + "Specify this field to update the device. " + "Referencing non-existing User Id will cause error. " + - "Omit this field to create new customer." ) + "Omit this field to create new customer.") @Override public UserId getId() { return super.getId(); @@ -169,6 +169,10 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H @JsonIgnore public String getTitle() { + return getTitle(email, firstName, lastName); + } + + public static String getTitle(String email, String firstName, String lastName) { String title = ""; if (isNotEmpty(firstName)) { title += firstName; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/exception/TenantNotFoundException.java b/common/data/src/main/java/org/thingsboard/server/common/data/exception/TenantNotFoundException.java new file mode 100644 index 0000000000..01005a2c36 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/exception/TenantNotFoundException.java @@ -0,0 +1,31 @@ +/** + * 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.exception; + +import lombok.Getter; +import org.thingsboard.server.common.data.id.TenantId; + +public class TenantNotFoundException extends RuntimeException { + + @Getter + private final TenantId tenantId; + + public TenantNotFoundException(TenantId tenantId) { + super("Tenant with id " + tenantId + " not found"); + this.tenantId = tenantId; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java index d976c36b00..de07d03c67 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java @@ -27,6 +27,6 @@ public enum NotificationType { NEW_PLATFORM_VERSION, ENTITIES_LIMIT, API_USAGE_LIMIT, - RULE_ENGINE + RULE_NODE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java index 4328885509..416ce73abe 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.CustomerId; @@ -59,10 +60,12 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati public Map getTemplateData() { return mapOf( "action", action, + "assigneeTitle", User.getTitle(assigneeEmail, assigneeFirstName, assigneeLastName), "assigneeFirstName", assigneeFirstName, "assigneeLastName", assigneeLastName, "assigneeEmail", assigneeEmail, "assigneeId", assigneeId != null ? assigneeId.toString() : null, + "userTitle", User.getTitle(userEmail, userFirstName, userLastName), "userEmail", userEmail, "userFirstName", userFirstName, "userLastName", userLastName, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java index 1122741888..00e8a0dc55 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.CustomerId; @@ -55,6 +56,7 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI return mapOf( "comment", comment, "action", action, + "userTitle", User.getTitle(userEmail, userFirstName, userLastName), "userEmail", userEmail, "userFirstName", userFirstName, "userLastName", userLastName, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java index 43be1e3e2a..c4501d828e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -40,6 +41,7 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI private CustomerId entityCustomerId; private UUID userId; + private String userTitle; private String userEmail; private String userFirstName; private String userLastName; @@ -52,6 +54,7 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI "entityName", entityName, "actionType", actionType.name().toLowerCase(), "userId", userId.toString(), + "userTitle", userTitle, "userEmail", userEmail, "userFirstName", userFirstName, "userLastName", userLastName diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java index 076d51f62c..ed9dee71b6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.notification.targets.slack; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -24,6 +25,7 @@ import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import static org.apache.commons.lang3.StringUtils.isEmpty; @@ -31,23 +33,34 @@ import static org.apache.commons.lang3.StringUtils.isEmpty; @NoArgsConstructor @AllArgsConstructor @Builder +@JsonIgnoreProperties(ignoreUnknown = true) public class SlackConversation implements NotificationRecipient { + @NotNull + private SlackConversationType type; @NotEmpty private String id; @NotEmpty - private String title; + private String name; - private String shortName; private String wholeName; private String email; + @Override + public String getTitle() { + if (type == SlackConversationType.DIRECT) { + return StringUtils.defaultIfEmpty(wholeName, name); + } else { + return name; + } + } + @JsonIgnore @Override public String getFirstName() { String firstName = StringUtils.contains(wholeName, " ") ? wholeName.split(" ")[0] : wholeName; if (isEmpty(firstName)) { - firstName = shortName; + firstName = name; } return firstName; } @@ -58,4 +71,9 @@ public class SlackConversation implements NotificationRecipient { return StringUtils.contains(wholeName, " ") ? wholeName.split(" ")[1] : null; } + @JsonIgnore + public String getPointer() { + return type == SlackConversationType.DIRECT ? "@" : "#"; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java index 85d5621ec4..d8d17613de 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.common.data.util; -import com.google.common.collect.ImmutableMap; - import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -63,8 +61,12 @@ public class CollectionsUtil { return map; } - public static Map unmodifiableMapOf(Object... kvs) { + public static Map unmodifiableMapOf(Object... kvs) { return Collections.unmodifiableMap(mapOf(kvs)); } + public static boolean emptyOrContains(Collection collection, V element) { + return isEmpty(collection) || collection.contains(element); + } + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntityActionTrigger.java b/common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntityActionTrigger.java new file mode 100644 index 0000000000..47d1789fec --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntityActionTrigger.java @@ -0,0 +1,47 @@ +/** + * 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.msg.notification.trigger; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; + +@Data +@Builder +public class EntityActionTrigger implements NotificationRuleTrigger { + + private final TenantId tenantId; + private final EntityId entityId; + private final HasName entity; + private final ActionType actionType; + private final User user; + + @Override + public NotificationRuleTriggerType getType() { + return NotificationRuleTriggerType.ENTITY_ACTION; + } + + @Override + public EntityId getOriginatorEntityId() { + return entityId; + } + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java index f186367b4c..08f2e849f8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.exception.TenantNotFoundException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -368,7 +369,7 @@ public class HashPartitionService implements PartitionService { } } if (routingInfo == null) { - throw new RuntimeException("Tenant not found!"); + throw new TenantNotFoundException(tenantId); } switch (serviceType) { case TB_RULE_ENGINE: diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java index 5dfaa70e12..8a5857e6a7 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.exception.TenantNotFoundException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -122,6 +123,8 @@ public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, parent.getTenantId(), parent.getId()) .newByTopic(msgProducer.getDefaultTopic()); msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), statsMsg.build()), null); + } catch (TenantNotFoundException e) { + log.debug("Couldn't report usage stats for non-existing tenant: {}", e.getTenantId()); } catch (Exception e) { log.warn("Failed to report usage stats for tenant {}", parent.getTenantId(), e); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java index 9ad71fe7e1..b0a2306408 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java @@ -67,7 +67,7 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl return notificationTargetDao.saveAndFlush(tenantId, notificationTarget); } catch (Exception e) { checkConstraintViolation(e, Map.of( - "uq_notification_target_name", "Notification target with such name already exists" + "uq_notification_target_name", "Recipients group with such name already exists" )); throw e; } @@ -109,7 +109,7 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl List users = ((UserListFilter) usersFilter).getUsersIds().stream() .limit(pageLink.getPageSize()) .map(UserId::new).map(userId -> userService.findUserById(tenantId, userId)) - .collect(Collectors.toList()); + .filter(Objects::nonNull).collect(Collectors.toList()); return new PageData<>(users, 1, users.size(), false); } case CUSTOMER_USERS: { @@ -179,10 +179,10 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl @Override public void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id) { if (notificationRequestDao.existsByTenantIdAndStatusAndTargetId(tenantId, NotificationRequestStatus.SCHEDULED, id)) { - throw new IllegalArgumentException("Notification target is referenced by scheduled notification request"); + throw new IllegalArgumentException("Recipients group is referenced by scheduled notification request"); } if (notificationRuleDao.existsByTenantIdAndTargetId(tenantId, id)) { - throw new IllegalArgumentException("Notification target is being used in notification rule"); + throw new IllegalArgumentException("Recipients group is being used in notification rule"); } notificationTargetDao.removeById(tenantId, id.getId()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index 038ffe342a..b9ab9a322e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -71,7 +71,7 @@ public class DefaultNotifications { .type(NotificationType.ENTITIES_LIMIT) .subject("${entityType}s limit will be reached soon for tenant ${tenantName}") .text("${entityType}s usage: ${currentCount}/${limit} (${percents}%)") - .icon("warning").color("#D6D634") + .icon("warning").color("#F9D916") .rule(DefaultRule.builder() .name("Entities count limit (sysadmin)") .triggerConfig(EntitiesLimitNotificationRuleTriggerConfig.builder() @@ -82,7 +82,7 @@ public class DefaultNotifications { .build(); public static final DefaultNotification entitiesLimitForTenant = entitiesLimitForSysadmin.toBuilder() .name("Entities count limit notification for tenant") - .subject("${entityType}s limit will be reached soon") + .subject("WARNING: ${entityType}s limit will be reached soon") .rule(entitiesLimitForSysadmin.getRule().toBuilder() .name("Entities count limit") .description("Send notification to tenant admins when count of entities of some type reached 80% threshold of the limit") @@ -93,7 +93,7 @@ public class DefaultNotifications { .type(NotificationType.API_USAGE_LIMIT) .subject("${feature} feature will be disabled soon for tenant ${tenantName}") .text("Usage: ${currentValue} out of ${limit} ${unitLabel}s") - .icon("warning").color("#D6D634") + .icon("warning").color("#F9D916") .rule(DefaultRule.builder() .name("API feature warning (sysadmin)") .triggerConfig(ApiUsageLimitNotificationRuleTriggerConfig.builder() @@ -105,7 +105,7 @@ public class DefaultNotifications { .build(); public static final DefaultNotification apiFeatureWarningForTenant = apiFeatureWarningForSysadmin.toBuilder() .name("API feature warning notification for tenant") - .subject("${feature} feature will be disabled soon") + .subject("WARNING: ${feature} feature will be disabled soon") .rule(apiFeatureWarningForSysadmin.getRule().toBuilder() .name("API feature warning") .description("Send notification to tenant admins on API feature usage WARNING state") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java index 145905593f..32c6d5388e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java @@ -22,7 +22,6 @@ 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.rule.NotificationRuleInfo; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity; @@ -42,7 +41,10 @@ public interface NotificationRuleRepository extends JpaRepository 0 FROM NotificationRuleEntity r WHERE r.tenantId = :tenantId " + + "AND CAST(r.recipientsConfig AS text) LIKE concat('%', :searchString, '%')") + boolean existsByTenantIdAndRecipientsConfigContaining(@Param("tenantId") UUID tenantId, + @Param("searchString") String searchString); List findAllByTenantIdAndTriggerType(UUID tenantId, NotificationRuleTriggerType triggerType); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java index 7666157400..acaff9b238 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -54,4 +54,6 @@ public interface MailService { void testConnection(TenantId tenantId) throws Exception; + boolean isConfigured(TenantId tenantId); + } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java index 129fe6bc05..18411ade1d 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java @@ -27,8 +27,6 @@ public interface SlackService { List listConversations(TenantId tenantId, String token, SlackConversationType conversationType); - SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern); - String getToken(TenantId tenantId); } diff --git a/ui-ngx/src/app/shared/models/notification.models.ts b/ui-ngx/src/app/shared/models/notification.models.ts index 2bb00fe745..23a6c08304 100644 --- a/ui-ngx/src/app/shared/models/notification.models.ts +++ b/ui-ngx/src/app/shared/models/notification.models.ts @@ -101,9 +101,10 @@ interface SlackNotificationDeliveryMethodConfig { export interface SlackConversation { id: string; title: string; - shortName: string; + name: string; wholeName: string; email: string; + type: string; } export interface NotificationRule extends Omit, 'label'>{ @@ -443,7 +444,7 @@ export enum NotificationType { ENTITIES_LIMIT = 'ENTITIES_LIMIT', API_USAGE_LIMIT = 'API_USAGE_LIMIT', NEW_PLATFORM_VERSION = 'NEW_PLATFORM_VERSION', - RULE_ENGINE = 'RULE_ENGINE' + RULE_NODE = 'RULE_NODE' } export const NotificationTypeIcons = new Map([ @@ -543,10 +544,10 @@ export const NotificationTemplateTypeTranslateMap = new Map
-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; - * *alarmType* - alarm type; - * *action* - one of: 'created', 'severity changed', 'acknowledged', 'cleared', 'deleted'; - * *alarmId* - the alarm id as uuid string; - * *alarmSeverity* - alarm severity (lower case); - * *alarmStatus* - the alarm status; - * *alarmOriginatorEntityType* - the entity type of the alarm originator, e.g. 'Device'; - * *alarmOriginatorName* - the name of the alarm originator, e.g. 'Sensor T1'; - * *alarmOriginatorId* - the alarm originator entity id as uuid string; - -Parameter names must be wrapped using `${...}`. For example: `${action}`. +* `alarmType` - alarm type; +* `action` - one of: 'created', 'severity changed', 'acknowledged', 'cleared', 'deleted'; +* `alarmId` - the alarm id as uuid string; +* `alarmSeverity` - alarm severity (lower case); +* `alarmStatus` - the alarm status; +* `alarmOriginatorEntityType` - the entity type of the alarm originator, e.g. 'Device'; +* `alarmOriginatorName` - the name of the alarm originator, e.g. 'Sensor T1'; +* `alarmOriginatorId` - the alarm originator entity id as uuid string; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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: `${action}`. 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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
##### Examples - * Let's assume the notification about new alarm with type 'High Temperature' for device 'Sensor A'. The following template: +Let's assume the notification about new alarm with type 'High Temperature' for device 'Sensor A'. +The following template: ```text Alarm '${alarmType}' - ${action:upperCase} @@ -42,9 +45,10 @@ will be transformed to: ```text Alarm 'High Temperature' - CREATED -{:copy-code} ``` +
+ The following template: ```text @@ -56,7 +60,6 @@ will be transformed to: ```text Device - Sensor A -{:copy-code} ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md b/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md index c4ff27d333..aa80b13b35 100644 --- a/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md +++ b/ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md @@ -3,42 +3,47 @@

-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; - * *alarmType* - alarm type; - * *alarmId* - the alarm id as uuid string; - * *alarmSeverity* - alarm severity (lower case); - * *alarmStatus* - the alarm status; - * *alarmOriginatorEntityType* - the entity type of the alarm originator, e.g. 'Device'; - * *alarmOriginatorName* - the name of the alarm originator, e.g. 'Sensor T1'; - * *alarmOriginatorId* - the alarm originator entity id as uuid string; - * *assigneeEmail* - email of the assignee; - * *assigneeFirstName* - first name of the assignee; - * *assigneeLastName* - last name of the assignee; - * *assigneeId* - the id of the assignee as uuid string; - * *userEmail* - email of the user who made the action; - * *userFirstName* - first name of the user who made the action; - * *userLastName* - last name of the user who made the action; - * *action* - one of: 'assigned', 'unassigned'; - -Parameter names must be wrapped using `${...}`. For example: `${action}`. +* `alarmType` - alarm type; +* `alarmId` - the alarm id as uuid string; +* `alarmSeverity` - alarm severity (lower case); +* `alarmStatus` - the alarm status; +* `alarmOriginatorEntityType` - the entity type of the alarm originator, e.g. 'Device'; +* `alarmOriginatorName` - the name of the alarm originator, e.g. 'Sensor T1'; +* `alarmOriginatorId` - the alarm originator entity id as uuid string; +* `assigneeTitle` - title of the assignee; +* `assigneeEmail` - email of the assignee; +* `assigneeFirstName` - first name of the assignee; +* `assigneeLastName` - last name of the assignee; +* `assigneeId` - the id of the assignee as uuid string; +* `userTitle` - title of the user who made the action; +* `userEmail` - email of the user who made the action; +* `userFirstName` - first name of the user who made the action; +* `userLastName` - last name of the user who made the action; +* `action` - one of: 'assigned', 'unassigned'; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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: `${action}`. 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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
##### Examples - * Let's assume the notification about alarm with type 'High Temperature' for device 'Sensor A' was assigned to user 'John Doe'. The following template: +Let's assume the notification about alarm with type 'High Temperature' for device 'Sensor A' was assigned +to user 'John Doe'. The following template: ```text Alarm '${alarmType}' - ${action:upperCase} @@ -49,9 +54,10 @@ will be transformed to: ```text Alarm 'High Temperature' - ASSIGNED -{:copy-code} ``` +
+ The following template: ```text @@ -63,7 +69,6 @@ will be transformed to: ```text Alarm 'High Temperature' (Critical) was assigned to user -{:copy-code} ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md b/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md index 8a008ecf27..150e76babc 100644 --- a/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md +++ b/ui-ngx/src/assets/help/en_US/notification/alarm_comment.md @@ -3,40 +3,43 @@

-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; - * *alarmType* - alarm type; - * *alarmId* - the alarm id as uuid string; - * *alarmSeverity* - alarm severity (lower case); - * *alarmStatus* - the alarm status; - * *alarmOriginatorEntityType* - the entity type of the alarm originator, e.g. 'Device'; - * *alarmOriginatorName* - the name of the alarm originator, e.g. 'Sensor T1'; - * *alarmOriginatorId* - the alarm originator entity id as uuid string; - * *comment* - text of the comment; - * *action* - one of: 'added', 'updated'; - * *userEmail* - email of the user who made the action; - * *userFirstName* - first name of the user who made the action; - * *userLastName* - last name of the user who made the action; - -Parameter names must be wrapped using `${...}`. For example: `${action}`. +* `alarmType` - alarm type; +* `alarmId` - the alarm id as uuid string; +* `alarmSeverity` - alarm severity (lower case); +* `alarmStatus` - the alarm status; +* `alarmOriginatorEntityType` - the entity type of the alarm originator, e.g. 'Device'; +* `alarmOriginatorName` - the name of the alarm originator, e.g. 'Sensor T1'; +* `alarmOriginatorId` - the alarm originator entity id as uuid string; +* `comment` - text of the comment; +* `action` - one of: 'added', 'updated'; +* `userTitle` - title of the user who made the action; +* `userEmail` - email of the user who made the action; +* `userFirstName` - first name of the user who made the action; +* `userLastName` - last name of the user who made the action; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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: `${action}`. 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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
##### Examples - * Let's assume the notification about alarm with type 'High Temperature' for device 'Sensor A' was assigned to user 'John Doe'. - The following template: +Let's assume the notification about alarm with type 'High Temperature' for device 'Sensor A' was assigned +to user 'John Doe'. The following template: ```text Alarm '${alarmType}' - comment ${action} @@ -47,9 +50,10 @@ will be transformed to: ```text Alarm 'High Temperature' - comment added -{:copy-code} ``` +
+ The following template: ```text @@ -61,7 +65,6 @@ will be transformed to: ```text Alarm 'High Temperature' (Critical) was commented -{:copy-code} ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md b/ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md index 29bd757067..e6f7462f94 100644 --- a/ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md +++ b/ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md @@ -3,34 +3,39 @@

-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; - * *feature* - API feature for which the limit is applied; one of: 'Device API', 'Telemetry persistence', 'Rule Engine execution', 'JavaScript functions execution', 'Email messages', 'SMS messages', 'Alarms'; - * *status* - one of: 'enabled', 'warning', 'disabled'; - * *unitLabel* - name of the limited unit; one of: 'message', 'data point', 'Rule Engine execution', 'JavaScript execution', 'email message', 'SMS message', 'alarm'; - * *limit* - the limit on used feature units; - * *currentValue* - current number of used units; - * *tenantId* - id of the tenant; - * *tenantName* - name of the tenant; - -Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +* `feature` - API feature for which the limit is applied; one of: 'Device API', 'Telemetry persistence', + 'Rule Engine execution', 'JavaScript functions execution', 'Email messages', 'SMS messages', 'Alarms'; +* `status` - one of: 'enabled', 'warning', 'disabled'; +* `unitLabel` - name of the limited unit; one of: 'message', 'data point', 'Rule Engine execution', + 'JavaScript execution', 'email message', 'SMS message', 'alarm'; +* `limit` - the limit on used feature units; +* `currentValue` - current number of used units; +* `tenantId` - id of the tenant; +* `tenantName` - name of the tenant; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
##### Examples -Let's assume tenant's devices pushed 8K messages with the max allowed number of 10K and warn threshold in tenant profile set to 0.8 (80%). The following template: +Let's assume tenant's devices pushed 8K messages with the max allowed number of 10K +and warn threshold in tenant profile set to 0.8 (80%). The following template: ```text ${feature} feature - ${status:upperCase} (usage: ${currentValue} out of ${limit} ${unitLabel}s) @@ -41,7 +46,6 @@ will be transformed to: ```text Device API feature - WARNING (usage: 8000 out of 10000 messages) -{:copy-code} ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/device_activity.md b/ui-ngx/src/assets/help/en_US/notification/device_activity.md index 5e69a0c634..346cae8e8e 100644 --- a/ui-ngx/src/assets/help/en_US/notification/device_activity.md +++ b/ui-ngx/src/assets/help/en_US/notification/device_activity.md @@ -3,36 +3,36 @@

-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; - * *deviceId* - the device id as uuid string; - * *deviceName* - the device name; - * *deviceLabel* - the device label; - * *deviceType* - the device type; - * *eventType* - one of: 'inactive', 'active'; - -Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +* `deviceId` - the device id as uuid string; +* `deviceName` - the device name; +* `deviceLabel` - the device label; +* `deviceType` - the device type; +* `eventType` - one of: 'inactive', 'active'; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
##### Examples -Let's assume the notification about inactive thermometer device 'Sensor T1'. +Let's assume the notification about inactive thermometer device 'Sensor T1'. The following template: -Template message: `` - ```text Device '${deviceName}' inactive {:copy-code} @@ -42,11 +42,10 @@ will be transformed to: ```text Device 'Sensor T1' inactive -{:copy-code} ``` +
-
The following template: ```text @@ -58,7 +57,7 @@ will be transformed to: ```text Thermometer 'Sensor T1' became inactive -{:copy-code} ``` +

diff --git a/ui-ngx/src/assets/help/en_US/notification/entities_limit.md b/ui-ngx/src/assets/help/en_US/notification/entities_limit.md index da351ad30f..6b358d1cc3 100644 --- a/ui-ngx/src/assets/help/en_US/notification/entities_limit.md +++ b/ui-ngx/src/assets/help/en_US/notification/entities_limit.md @@ -3,33 +3,36 @@

-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; - * *entityType* - one of: 'Device', 'Asset', 'User', etc.; - * *currentCount* - the current count of entities; - * *limit* - the limit on number of entities; - * *percents* - the percent from the notification rule configuration; - * *tenantId* - id of the tenant; - * *tenantName* - name of the tenant; - -Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +* `entityType` - one of: 'Device', 'Asset', 'User', etc.; +* `currentCount` - the current count of entities; +* `limit` - the limit on number of entities; +* `percents` - the percent from the notification rule configuration; +* `tenantId` - id of the tenant; +* `tenantName` - name of the tenant; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
##### Examples -Let's assume the tenant created 400 devices with the max allowed number is 500 and rule threshold 0.8 (80%). The following template: +Let's assume the tenant created 400 devices with the max allowed number is 500 and rule threshold 0.8 (80%). +The following template: ```text ${entityType:capitalize}s usage: ${currentCount}/${limit} (${percents}%) @@ -40,7 +43,6 @@ will be transformed to: ```text Devices usage: 400/500 (80%) -{:copy-code} ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/entity_action.md b/ui-ngx/src/assets/help/en_US/notification/entity_action.md index 06e3fe2d4e..0907a55220 100644 --- a/ui-ngx/src/assets/help/en_US/notification/entity_action.md +++ b/ui-ngx/src/assets/help/en_US/notification/entity_action.md @@ -3,35 +3,38 @@

-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; - * *entityType* - the entity type, e.g. 'Device'; - * *entityId* - the entity id as uuid string; - * *entityName* - the name of the entity; - * *actionType* - one of: 'added', 'updated', 'deleted'; - * *userId* - id of the user who made the action; - * *userEmail* - email of the user who made the action; - * *userFirstName* - first name of the user who made the action; - * *userLastName* - last name of the user who made the action; - -Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +* `entityType` - the entity type, e.g. 'Device'; +* `entityId` - the entity id as uuid string; +* `entityName` - the name of the entity; +* `actionType` - one of: 'added', 'updated', 'deleted'; +* `userId` - id of the user who made the action; +* `userTitle` - title of the user who made the action; +* `userEmail` - email of the user who made the action; +* `userFirstName` - first name of the user who made the action; +* `userLastName` - last name of the user who made the action; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
##### Examples -Let's assume the notification about device 'T1' was added by user 'john.doe@gmail.com'. +Let's assume the notification about device 'T1' was added by user 'john.doe@gmail.com'. The following template: ```text @@ -43,22 +46,21 @@ will be transformed to: ```text Device was added! -{:copy-code} ``` -
+
+ The following template: ```text -${entityType} '${entityName}' was ${actionType} by user '${originatorUserName}'! +${entityType} '${entityName}' was ${actionType} by user ${userEmail} {:copy-code} ``` will be transformed to: ```text -Device 'T1' was added by user 'john.doe@gmail.com'! -{:copy-code} +Device 'T1' was added by user john.doe@gmail.com ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/general.md b/ui-ngx/src/assets/help/en_US/notification/general.md index 44c8e6aafb..efbff88b8d 100644 --- a/ui-ngx/src/assets/help/en_US/notification/general.md +++ b/ui-ngx/src/assets/help/en_US/notification/general.md @@ -3,21 +3,23 @@

-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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}`. +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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
@@ -34,7 +36,6 @@ will be transformed to: ```text Hi, John! -{:copy-code} ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/new_platform_version.md b/ui-ngx/src/assets/help/en_US/notification/new_platform_version.md index b71d70d6cc..d2c600d202 100644 --- a/ui-ngx/src/assets/help/en_US/notification/new_platform_version.md +++ b/ui-ngx/src/assets/help/en_US/notification/new_platform_version.md @@ -3,26 +3,28 @@

-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; - * *latestVersion* - the latest platform version available; - * *latestVersionReleaseNotesUrl* - release notes link for latest version; - * *upgradeInstructionsUrl* - upgrade instructions link for latest version; - * *currentVersion* - the current platform version - * *currentVersionReleaseNotesUrl* - release notes link for current version; - -Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +* `latestVersion` - the latest platform version available; +* `latestVersionReleaseNotesUrl` - release notes link for latest version; +* `upgradeInstructionsUrl` - upgrade instructions link for latest version; +* `currentVersion` - the current platform version +* `currentVersionReleaseNotesUrl` - release notes link for current version; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
@@ -39,7 +41,6 @@ will be transformed to: ```text New version 3.5.0 is available. Current version is 3.4.4 -{:copy-code} ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/rule_engine.md b/ui-ngx/src/assets/help/en_US/notification/rule_engine.md deleted file mode 100644 index 1493847139..0000000000 --- a/ui-ngx/src/assets/help/en_US/notification/rule_engine.md +++ /dev/null @@ -1,54 +0,0 @@ -#### Rule engine notification templatization - -
-
- -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 referenced using the metadata key name; - * values from the incoming message data referenced using the data key name; - * *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}` - -
- -##### 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 -``` - -
-
diff --git a/ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md b/ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md index 1d22847bed..0a738d05c1 100644 --- a/ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md +++ b/ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md @@ -3,29 +3,31 @@

-Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. +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: - * *recipientEmail* - email of the recipient; - * *recipientFirstName* - first name of the recipient; - * *recipientLastName* - last name of the recipient; - * *componentType* - one of: 'Rule chain', 'Rule node'; - * *componentId* - the component id as uuid string; - * *componentName* - the rule chain or rule node name; - * *ruleChainId* - the rule chain id as uuid string; - * *ruleChainName* - the rule chain name; - * *eventType* - one of: 'started', 'updated', 'stopped'; - * *action* - one of: 'start', 'update', 'stop'; - * *error* - the error text; - -Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. +* `componentType` - one of: 'Rule chain', 'Rule node'; +* `componentId` - the component id as uuid string; +* `componentName` - the rule chain or rule node name; +* `ruleChainId` - the rule chain id as uuid string; +* `ruleChainName` - the rule chain name; +* `eventType` - one of: 'started', 'updated', 'stopped'; +* `action` - one of: 'start', 'update', 'stop'; +* `error` - the error text; +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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}` +* `upperCase`, for example - `${recipientFirstName:upperCase}` +* `lowerCase`, for example - `${recipientFirstName:lowerCase}` +* `capitalize`, for example - `${recipientFirstName:capitalize}`
@@ -42,7 +44,6 @@ will be transformed to: ```text Rule node 'Export to Kafka' - start failure:
Connection refused! -{:copy-code} ```
diff --git a/ui-ngx/src/assets/help/en_US/notification/rule_node.md b/ui-ngx/src/assets/help/en_US/notification/rule_node.md new file mode 100644 index 0000000000..9eb7bd3b71 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/notification/rule_node.md @@ -0,0 +1,57 @@ +#### Rule engine notification templatization + +
+
+ +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 referenced using the metadata key name; +* values from the incoming message data referenced using the data key name; +* `originatorType` - type of the originator, e.g. 'Device'; +* `originatorId` - id of the originator +* `msgType` - type of the message +* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise); +* `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}` + +
+ +##### 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 +``` + +
+
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 346959a472..0ca7e48355 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2960,7 +2960,7 @@ "entity-action": "Entity action", "general": "General", "rule-engine-lifecycle-event": "Rule engine lifecycle event", - "rule-engine": "Rule engine", + "rule-node": "Rule node", "new-platform-version": "New platform version" }, "templates": "Templates",