Browse Source

Merge branch 'develop/3.5' of https://github.com/thingsboard/thingsboard into assignee

pull/8370/head
Seraphym-Tuhai 3 years ago
parent
commit
0483306c83
  1. 15
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  2. 25
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  3. 5
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  4. 1
      application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
  5. 6
      application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
  6. 5
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
  7. 11
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
  8. 8
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  9. 6
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java
  10. 73
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
  12. 23
      application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
  13. 4
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  14. 31
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  15. 8
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  16. 37
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  17. 25
      application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java
  18. 13
      application/src/test/java/org/thingsboard/server/service/stats/DevicesStatisticsTest.java
  19. 6
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  20. 31
      common/data/src/main/java/org/thingsboard/server/common/data/exception/TenantNotFoundException.java
  21. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java
  22. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java
  23. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java
  24. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java
  25. 24
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java
  26. 8
      common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java
  27. 47
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntityActionTrigger.java
  28. 3
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  29. 3
      common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java
  30. 8
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  31. 8
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java
  32. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java
  33. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java
  34. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java
  35. 11
      ui-ngx/src/app/shared/models/notification.models.ts
  36. 43
      ui-ngx/src/assets/help/en_US/notification/alarm.md
  37. 59
      ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md
  38. 53
      ui-ngx/src/assets/help/en_US/notification/alarm_comment.md
  39. 40
      ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md
  40. 39
      ui-ngx/src/assets/help/en_US/notification/device_activity.md
  41. 36
      ui-ngx/src/assets/help/en_US/notification/entities_limit.md
  42. 48
      ui-ngx/src/assets/help/en_US/notification/entity_action.md
  43. 19
      ui-ngx/src/assets/help/en_US/notification/general.md
  44. 31
      ui-ngx/src/assets/help/en_US/notification/new_platform_version.md
  45. 54
      ui-ngx/src/assets/help/en_US/notification/rule_engine.md
  46. 37
      ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md
  47. 57
      ui-ngx/src/assets/help/en_US/notification/rule_node.md
  48. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

15
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.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.NotificationTargetType; import org.thingsboard.server.common.data.notification.targets.NotificationTargetType;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
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.targets.slack.SlackNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.dao.notification.NotificationRequestService; import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationService;
import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationSettingsService;
@ -230,7 +232,8 @@ public class NotificationController extends BaseController {
NotificationTargetType targetType = target.getConfiguration().getType(); NotificationTargetType targetType = target.getConfiguration().getType();
if (targetType == NotificationTargetType.PLATFORM_USERS) { if (targetType == NotificationTargetType.PLATFORM_USERS) {
PageData<User> recipients = notificationTargetService.findRecipientsForNotificationTargetConfig(user.getTenantId(), PageData<User> 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(); recipientsCount = (int) recipients.getTotalElements();
recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList()); recipientsPart = recipients.getData().stream().map(r -> (NotificationRecipient) r).collect(Collectors.toList());
} else { } else {
@ -240,7 +243,15 @@ public class NotificationController extends BaseController {
firstRecipient.putIfAbsent(targetType, !recipientsPart.isEmpty() ? recipientsPart.get(0) : null); firstRecipient.putIfAbsent(targetType, !recipientsPart.isEmpty() ? recipientsPart.get(0) : null);
for (NotificationRecipient recipient : recipientsPart) { for (NotificationRecipient recipient : recipientsPart) {
if (recipientsPreview.size() < recipientsPreviewSize) { 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 { } else {
break; break;
} }

25
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.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.notification.trigger.EntitiesLimitTrigger; 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.dao.audit.AuditLogService;
import org.thingsboard.server.queue.notification.NotificationRuleProcessor; import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
@ -190,12 +191,6 @@ public class EntityActionService {
AlarmComment comment = extractParameter(AlarmComment.class, 0, additionalInfo); AlarmComment comment = extractParameter(AlarmComment.class, 0, additionalInfo);
metaData.putValue("comment", json.writeValueAsString(comment)); metaData.putValue("comment", json.writeValueAsString(comment));
} }
if (actionType == ActionType.ADDED && !tenantId.isSysTenantId()) {
notificationRuleProcessor.process(EntitiesLimitTrigger.builder()
.tenantId(tenantId)
.entityType(entityId.getEntityType())
.build());
}
ObjectNode entityNode; ObjectNode entityNode;
if (entity != null) { if (entity != null) {
entityNode = json.valueToTree(entity); entityNode = json.valueToTree(entity);
@ -242,12 +237,28 @@ public class EntityActionService {
entityNode = json.valueToTree(extractParameter(EntityRelation.class, 0, additionalInfo)); 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 (tenantId == null || tenantId.isNullUid()) {
if (entity instanceof HasTenantId) { if (entity instanceof HasTenantId) {
tenantId = ((HasTenantId) entity).getTenantId(); 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); tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null);
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);

5
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -364,6 +364,11 @@ public class DefaultMailService implements MailService {
mailSender.testConnection(); mailSender.testConnection();
} }
@Override
public boolean isConfigured(TenantId tenantId) {
return mailSender != null;
}
private String toEnabledValueLabel(ApiFeature apiFeature) { private String toEnabledValueLabel(ApiFeature apiFeature) {
switch (apiFeature) { switch (apiFeature) {
case DB: case DB:

1
application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java

@ -128,6 +128,7 @@ public class NotificationProcessingContext {
private Map<String, String> createTemplateContextForRecipient(NotificationRecipient recipient) { private Map<String, String> createTemplateContextForRecipient(NotificationRecipient recipient) {
return Map.of( return Map.of(
"recipientTitle", recipient.getTitle(),
"recipientEmail", Strings.nullToEmpty(recipient.getEmail()), "recipientEmail", Strings.nullToEmpty(recipient.getEmail()),
"recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()), "recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()),
"recipientLastName", Strings.nullToEmpty(recipient.getLastName()) "recipientLastName", Strings.nullToEmpty(recipient.getLastName())

6
application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java

@ -49,10 +49,8 @@ public class EmailNotificationChannel implements NotificationChannel<User, Email
@Override @Override
public void check(TenantId tenantId) throws Exception { public void check(TenantId tenantId) throws Exception {
try { if (!mailService.isConfigured(tenantId)) {
mailService.testConnection(tenantId); throw new RuntimeException("Mail server is not configured");
} catch (Exception e) {
throw new RuntimeException("Mail server is not available");
} }
} }

5
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigg
import java.util.Set; import java.util.Set;
import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service @Service
public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<AlarmAssignmentNotificationRuleTriggerConfig> { public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<AlarmAssignmentNotificationRuleTriggerConfig> {
@ -43,8 +44,8 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
return false; return false;
} }
Alarm alarm = JacksonUtil.fromString(trigger.getMsg().getData(), Alarm.class); Alarm alarm = JacksonUtil.fromString(trigger.getMsg().getData(), Alarm.class);
return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && return emptyOrContains(triggerConfig.getAlarmTypes(), alarm.getType()) &&
(isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) && emptyOrContains(triggerConfig.getAlarmSeverities(), alarm.getSeverity()) &&
(isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm)); (isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm));
} }

11
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 java.util.Set;
import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service @Service
public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<AlarmCommentNotificationRuleTriggerConfig> { public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<AlarmCommentNotificationRuleTriggerConfig> {
@ -53,8 +54,8 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
} }
} }
Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class); Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class);
return (isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType())) && return emptyOrContains(triggerConfig.getAlarmTypes(), alarm.getType()) &&
(isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity())) && emptyOrContains(triggerConfig.getAlarmSeverities(), alarm.getSeverity()) &&
(isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm)); (isEmpty(triggerConfig.getAlarmStatuses()) || AlarmStatusFilter.from(triggerConfig.getAlarmStatuses()).matches(alarm));
} }
@ -66,9 +67,9 @@ public class AlarmCommentTriggerProcessor implements RuleEngineMsgNotificationRu
return AlarmCommentNotificationInfo.builder() return AlarmCommentNotificationInfo.builder()
.comment(comment.getComment().get("text").asText()) .comment(comment.getComment().get("text").asText())
.action(msg.getType().equals(DataConstants.COMMENT_CREATED) ? "added" : "updated") .action(msg.getType().equals(DataConstants.COMMENT_CREATED) ? "added" : "updated")
.userEmail(trigger.getMsg().getMetaData().getValue("userEmail")) .userEmail(msg.getMetaData().getValue("userEmail"))
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName")) .userFirstName(msg.getMetaData().getValue("userFirstName"))
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName")) .userLastName(msg.getMetaData().getValue("userLastName"))
.alarmId(alarmInfo.getUuidId()) .alarmId(alarmInfo.getUuidId())
.alarmType(alarmInfo.getType()) .alarmType(alarmInfo.getType())
.alarmOriginator(alarmInfo.getOriginator()) .alarmOriginator(alarmInfo.getOriginator())

8
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.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.Alarm; 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.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter; import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo; 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.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule; 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.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger; 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.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service @Service
public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmTrigger, AlarmNotificationRuleTriggerConfig> { public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmTrigger, AlarmNotificationRuleTriggerConfig> {
@ -85,11 +85,11 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
} }
private boolean severityMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) { private boolean severityMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) {
return isEmpty(triggerConfig.getAlarmSeverities()) || triggerConfig.getAlarmSeverities().contains(alarm.getSeverity()); return emptyOrContains(triggerConfig.getAlarmSeverities(), alarm.getSeverity());
} }
private boolean typeMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) { private boolean typeMatches(Alarm alarm, AlarmNotificationRuleTriggerConfig triggerConfig) {
return isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType()); return emptyOrContains(triggerConfig.getAlarmTypes(), alarm.getType());
} }
@Override @Override

6
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java

@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.notification.rule.trigger.Notification
import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger; import org.thingsboard.server.common.msg.notification.trigger.ApiUsageLimitTrigger;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import static org.apache.commons.collections.CollectionUtils.isEmpty; import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -34,8 +34,8 @@ public class ApiUsageLimitTriggerProcessor implements NotificationRuleTriggerPro
@Override @Override
public boolean matchesFilter(ApiUsageLimitTrigger trigger, ApiUsageLimitNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(ApiUsageLimitTrigger trigger, ApiUsageLimitNotificationRuleTriggerConfig triggerConfig) {
return (isEmpty(triggerConfig.getApiFeatures()) || triggerConfig.getApiFeatures().contains(trigger.getState().getApiFeature())) && return emptyOrContains(triggerConfig.getApiFeatures(), trigger.getState().getApiFeature()) &&
(isEmpty(triggerConfig.getNotifyOn()) || triggerConfig.getNotifyOn().contains(trigger.getStatus())); emptyOrContains(triggerConfig.getNotifyOn(), trigger.getStatus());
} }
@Override @Override

73
application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java

@ -16,78 +16,47 @@
package org.thingsboard.server.service.notification.rule.trigger; package org.thingsboard.server.service.notification.rule.trigger;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo; import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo;
import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo; import org.thingsboard.server.common.data.notification.info.RuleOriginatedNotificationInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.notification.trigger.EntityActionTrigger;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import java.util.Optional; import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
import java.util.Set;
import java.util.UUID;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
@Service @Service
public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<EntityActionNotificationRuleTriggerConfig> { public class EntityActionTriggerProcessor implements NotificationRuleTriggerProcessor<EntityActionTrigger, EntityActionNotificationRuleTriggerConfig> {
@Override @Override
public boolean matchesFilter(RuleEngineMsgTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) { public boolean matchesFilter(EntityActionTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) {
String msgType = trigger.getMsg().getType(); return ((trigger.getActionType() == ActionType.ADDED && triggerConfig.isCreated())
if (msgType.equals(DataConstants.ENTITY_CREATED)) { || (trigger.getActionType() == ActionType.UPDATED && triggerConfig.isUpdated())
if (!triggerConfig.isCreated()) { || (trigger.getActionType() == ActionType.DELETED && triggerConfig.isDeleted()))
return false; && emptyOrContains(triggerConfig.getEntityTypes(), trigger.getEntityId().getEntityType());
}
} 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()));
} }
@Override @Override
public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) { public RuleOriginatedNotificationInfo constructNotificationInfo(EntityActionTrigger 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;
return EntityActionNotificationInfo.builder() return EntityActionNotificationInfo.builder()
.entityId(msg.getOriginator()) .entityId(trigger.getEntityId())
.entityName(msg.getMetaData().getValue("entityName")) .entityName(trigger.getEntity().getName())
.actionType(actionType) .actionType(trigger.getActionType())
.userId(UUID.fromString(msg.getMetaData().getValue("userId"))) .userId(trigger.getUser().getUuidId())
.userEmail(trigger.getMsg().getMetaData().getValue("userEmail")) .userTitle(trigger.getUser().getTitle())
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName")) .userEmail(trigger.getUser().getEmail())
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName")) .userFirstName(trigger.getUser().getFirstName())
.entityCustomerId(msg.getCustomerId()) .userLastName(trigger.getUser().getLastName())
.entityCustomerId(trigger.getEntity() instanceof HasCustomerId ?
((HasCustomerId) trigger.getEntity()).getCustomerId() :
trigger.getUser().getCustomerId())
.build(); .build();
} }
private static EntityType getEntityType(TbMsg msg) {
return Optional.ofNullable(msg.getMetaData().getValue("entityType"))
.map(EntityType::valueOf).orElse(null);
}
@Override @Override
public NotificationRuleTriggerType getTriggerType() { public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ENTITY_ACTION; return NotificationRuleTriggerType.ENTITY_ACTION;
} }
@Override
public Set<String> getSupportedMsgTypes() {
return Set.of(DataConstants.ENTITY_CREATED, DataConstants.ENTITY_UPDATED, DataConstants.ENTITY_DELETED);
}
} }

6
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 lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; 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.TenantProfile;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.TenantId; 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.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@Slf4j @Slf4j
@Service @Service
@ -46,7 +46,7 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService
if (tenantProfile != null) { if (tenantProfile != null) {
return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbRuleEngine()); return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbRuleEngine());
} else { } else {
throw new RuntimeException("Tenant not found!"); throw new TenantNotFoundException(tenantId);
} }
} }
} }

23
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.methods.response.users.UsersListResponse;
import com.slack.api.model.ConversationType; import com.slack.api.model.ConversationType;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.slack.SlackService; import org.thingsboard.rule.engine.api.slack.SlackService;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -44,8 +43,6 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultSlackService implements SlackService { public class DefaultSlackService implements SlackService {
@ -81,15 +78,11 @@ public class DefaultSlackService implements SlackService {
.filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot()) .filter(user -> !user.isDeleted() && !user.isStranger() && !user.isBot())
.map(user -> { .map(user -> {
SlackConversation conversation = new SlackConversation(); SlackConversation conversation = new SlackConversation();
conversation.setType(conversationType);
conversation.setId(user.getId()); conversation.setId(user.getId());
conversation.setShortName(user.getName()); conversation.setName(user.getName());
conversation.setWholeName(user.getProfile() != null ? user.getProfile().getRealNameNormalized() : user.getRealName()); conversation.setWholeName(user.getProfile() != null ? user.getProfile().getRealNameNormalized() : user.getRealName());
conversation.setEmail(user.getProfile() != null ? user.getProfile().getEmail() : null); 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; return conversation;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -107,10 +100,10 @@ public class DefaultSlackService implements SlackService {
.filter(channel -> !channel.isArchived()) .filter(channel -> !channel.isArchived())
.map(channel -> { .map(channel -> {
SlackConversation conversation = new SlackConversation(); SlackConversation conversation = new SlackConversation();
conversation.setType(conversationType);
conversation.setId(channel.getId()); conversation.setId(channel.getId());
conversation.setShortName(channel.getName()); conversation.setName(channel.getName());
conversation.setWholeName(channel.getNameNormalized()); conversation.setWholeName(channel.getNameNormalized());
conversation.setTitle("#" + channel.getName());
return conversation; return conversation;
}) })
.collect(Collectors.toList()); .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<SlackConversation> conversations = listConversations(tenantId, token, conversationType);
return conversations.stream()
.filter(conversation -> StringUtils.containsIgnoreCase(conversation.getTitle(), namePattern))
.findFirst().orElse(null);
}
@Override @Override
public String getToken(TenantId tenantId) { public String getToken(TenantId tenantId) {
NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId);

4
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.DeviceIdInfo;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; 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.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -453,6 +454,9 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
updateInactivityStateIfExpired(ts, deviceId, stateData); updateInactivityStateIfExpired(ts, deviceId, stateData);
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to update inactivity state", deviceId, e); log.warn("[{}] Failed to update inactivity state", deviceId, e);
if (e instanceof TenantNotFoundException) {
continue;
}
} }
Pair<AtomicInteger, AtomicInteger> tenantDevicesActivity = devicesActivity.computeIfAbsent(stateData.getTenantId(), Pair<AtomicInteger, AtomicInteger> tenantDevicesActivity = devicesActivity.computeIfAbsent(stateData.getTenantId(),
tenantId -> Pair.of(new AtomicInteger(), new AtomicInteger())); tenantId -> Pair.of(new AtomicInteger(), new AtomicInteger()));

31
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.NotificationRequestInfo;
import org.thingsboard.server.common.data.notification.NotificationRequestStats; import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationType; 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.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; 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 org.thingsboard.server.dao.DaoUtil;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -195,6 +200,32 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
new PageLink(limit, 0), unreadOnly).getData(); 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<NotificationRuleInfo> findNotificationRules() throws Exception {
PageLink pageLink = new PageLink(10);
return doGetTypedWithPageLink("/api/notification/rules?", new TypeReference<PageData<NotificationRuleInfo>>() {}, pageLink);
}
@Override @Override
protected NotificationApiWsClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { protected NotificationApiWsClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException {
NotificationApiWsClient wsClient = new NotificationApiWsClient(WS_URL + wsPort, token); NotificationApiWsClient wsClient = new NotificationApiWsClient(WS_URL + wsPort, token);

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

@ -362,13 +362,14 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
SlackNotificationTargetConfig slackTargetConfig = new SlackNotificationTargetConfig(); SlackNotificationTargetConfig slackTargetConfig = new SlackNotificationTargetConfig();
slackTargetConfig.setConversationType(SlackConversationType.DIRECT); slackTargetConfig.setConversationType(SlackConversationType.DIRECT);
SlackConversation slackConversation = new SlackConversation(); SlackConversation slackConversation = new SlackConversation();
slackConversation.setType(SlackConversationType.DIRECT);
slackConversation.setId("U1234567"); slackConversation.setId("U1234567");
slackConversation.setTitle("@jdoe (John Doe)"); slackConversation.setName("jdoe");
slackConversation.setWholeName("John Doe"); slackConversation.setWholeName("John Doe");
slackTargetConfig.setConversation(slackConversation); slackTargetConfig.setConversation(slackConversation);
slackTarget.setConfiguration(slackTargetConfig); slackTarget.setConfiguration(slackTargetConfig);
slackTarget = saveNotificationTarget(slackTarget); slackTarget = saveNotificationTarget(slackTarget);
recipients.add(slackConversation.getTitle()); recipients.add("@" + slackConversation.getWholeName());
NotificationTemplate notificationTemplate = new NotificationTemplate(); NotificationTemplate notificationTemplate = new NotificationTemplate();
notificationTemplate.setNotificationType(NotificationType.GENERAL); notificationTemplate.setNotificationType(NotificationType.GENERAL);
@ -500,8 +501,9 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationTarget.setName(conversationName + " in Slack"); notificationTarget.setName(conversationName + " in Slack");
SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig(); SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig();
targetConfig.setConversation(SlackConversation.builder() targetConfig.setConversation(SlackConversation.builder()
.type(SlackConversationType.DIRECT)
.id(conversationId) .id(conversationId)
.title(conversationName) .name(conversationName)
.build()); .build());
notificationTarget.setConfiguration(targetConfig); notificationTarget.setConfiguration(targetConfig);
notificationTarget = saveNotificationTarget(notificationTarget); notificationTarget = saveNotificationTarget(notificationTarget);

37
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.notification; package org.thingsboard.server.service.notification;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BooleanNode; import com.fasterxml.jackson.databind.node.BooleanNode;
import org.junit.Before; 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.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; 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.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; 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.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig; 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.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.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; 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.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; 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.notification.NotificationRequestService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DaoSqlTest; 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 org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -436,7 +431,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
String name = "device " + i; String name = "device " + i;
createDevice(name, name); createDevice(name, name);
} }
await().atMost(5, TimeUnit.SECONDS) await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(notificationRequestsLimit); assertThat(getMyNotifications(false, 100)).size().isEqualTo(notificationRequestsLimit);
}); });
@ -474,21 +469,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
}, (notification, r) -> check.accept(notification)); }, (notification, r) -> check.accept(notification));
} }
private NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) { private PageData<NotificationRequestInfo> findNotificationRequests(EntityType originatorType) {
NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, NotificationDeliveryMethod.WEB); return notificationRequestService.findNotificationRequestsInfosByTenantIdAndOriginatorType(tenantId, originatorType, new PageLink(100));
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 DeviceProfile createDeviceProfileWithAlarmRules(String alarmType) { private DeviceProfile createDeviceProfileWithAlarmRules(String alarmType) {
@ -526,17 +508,4 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
return deviceProfile; return deviceProfile;
} }
private NotificationRule saveNotificationRule(NotificationRule notificationRule) {
return doPost("/api/notification/rule", notificationRule, NotificationRule.class);
}
private PageData<NotificationRuleInfo> findNotificationRules() throws Exception {
PageLink pageLink = new PageLink(10);
return doGetTypedWithPageLink("/api/notification/rules?", new TypeReference<PageData<NotificationRuleInfo>>() {}, pageLink);
}
private PageData<NotificationRequestInfo> findNotificationRequests(EntityType originatorType) {
return notificationRequestService.findNotificationRequestsInfosByTenantIdAndOriginatorType(tenantId, originatorType, new PageLink(100));
}
} }

25
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.springframework.test.web.servlet.ResultMatcher;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; 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.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.notification.NotificationTargetDao; import org.thingsboard.server.dao.notification.NotificationTargetDao;
import org.thingsboard.server.dao.service.DaoSqlTest; 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; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest @DaoSqlTest
public class NotificationTargetApiTest extends AbstractControllerTest { public class NotificationTargetApiTest extends AbstractNotificationApiTest {
@Autowired @Autowired
private NotificationTargetDao notificationTargetDao; private NotificationTargetDao notificationTargetDao;
@ -148,6 +149,26 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
assertThat(notificationTargetDao.findByTenantIdAndPageLink(differentTenantId, new PageLink(10)).getData()).isEmpty(); 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 { private String saveAndGetError(NotificationTarget notificationTarget, ResultMatcher statusMatcher) throws Exception {
return getErrorMessage(save(notificationTarget, statusMatcher)); return getErrorMessage(save(notificationTarget, statusMatcher));
} }

13
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.service.DaoSqlTest;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.state.DeviceStateService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -35,17 +36,14 @@ import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest @DaoSqlTest
@TestPropertySource(properties = { @TestPropertySource(properties = {
"usage.stats.report.enabled=true", "usage.stats.report.enabled=true",
"transport.http.enabled=true",
"usage.stats.report.interval=2", "usage.stats.report.interval=2",
"usage.stats.gauge_report_interval=1", "usage.stats.gauge_report_interval=1",
"state.defaultStateCheckIntervalInSec=3", "state.defaultStateCheckIntervalInSec=3",
"state.defaultInactivityTimeoutInSec=10" "state.defaultInactivityTimeoutInSec=10"
}) })
public class DevicesStatisticsTest extends AbstractControllerTest { public class DevicesStatisticsTest extends AbstractControllerTest {
@ -53,6 +51,8 @@ public class DevicesStatisticsTest extends AbstractControllerTest {
private TbApiUsageStateService apiUsageStateService; private TbApiUsageStateService apiUsageStateService;
@Autowired @Autowired
private TimeseriesService timeseriesService; private TimeseriesService timeseriesService;
@Autowired
private DeviceStateService deviceStateService;
private ApiUsageStateId apiUsageStateId; private ApiUsageStateId apiUsageStateId;
@ -86,7 +86,7 @@ public class DevicesStatisticsTest extends AbstractControllerTest {
}); });
for (Device device : activeDevices) { for (Device device : activeDevices) {
postTelemetry(device.getName(), "{\"dp\":1}"); deviceStateService.onDeviceActivity(tenantId, device.getId(), System.currentTimeMillis());
} }
await().atMost(40, TimeUnit.SECONDS) await().atMost(40, TimeUnit.SECONDS)
@ -102,9 +102,4 @@ public class DevicesStatisticsTest extends AbstractControllerTest {
.get().stream().findFirst().flatMap(KvEntry::getLongValue).orElse(null); .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());
}
} }

6
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -74,7 +74,7 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
@ApiModelProperty(position = 1, value = "JSON object with the User Id. " + @ApiModelProperty(position = 1, value = "JSON object with the User Id. " +
"Specify this field to update the device. " + "Specify this field to update the device. " +
"Referencing non-existing User Id will cause error. " + "Referencing non-existing User Id will cause error. " +
"Omit this field to create new customer." ) "Omit this field to create new customer.")
@Override @Override
public UserId getId() { public UserId getId() {
return super.getId(); return super.getId();
@ -169,6 +169,10 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
@JsonIgnore @JsonIgnore
public String getTitle() { public String getTitle() {
return getTitle(email, firstName, lastName);
}
public static String getTitle(String email, String firstName, String lastName) {
String title = ""; String title = "";
if (isNotEmpty(firstName)) { if (isNotEmpty(firstName)) {
title += firstName; title += firstName;

31
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;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java

@ -27,6 +27,6 @@ public enum NotificationType {
NEW_PLATFORM_VERSION, NEW_PLATFORM_VERSION,
ENTITIES_LIMIT, ENTITIES_LIMIT,
API_USAGE_LIMIT, API_USAGE_LIMIT,
RULE_ENGINE RULE_NODE
} }

3
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.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; 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.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -59,10 +60,12 @@ public class AlarmAssignmentNotificationInfo implements RuleOriginatedNotificati
public Map<String, String> getTemplateData() { public Map<String, String> getTemplateData() {
return mapOf( return mapOf(
"action", action, "action", action,
"assigneeTitle", User.getTitle(assigneeEmail, assigneeFirstName, assigneeLastName),
"assigneeFirstName", assigneeFirstName, "assigneeFirstName", assigneeFirstName,
"assigneeLastName", assigneeLastName, "assigneeLastName", assigneeLastName,
"assigneeEmail", assigneeEmail, "assigneeEmail", assigneeEmail,
"assigneeId", assigneeId != null ? assigneeId.toString() : null, "assigneeId", assigneeId != null ? assigneeId.toString() : null,
"userTitle", User.getTitle(userEmail, userFirstName, userLastName),
"userEmail", userEmail, "userEmail", userEmail,
"userFirstName", userFirstName, "userFirstName", userFirstName,
"userLastName", userLastName, "userLastName", userLastName,

2
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.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; 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.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -55,6 +56,7 @@ public class AlarmCommentNotificationInfo implements RuleOriginatedNotificationI
return mapOf( return mapOf(
"comment", comment, "comment", comment,
"action", action, "action", action,
"userTitle", User.getTitle(userEmail, userFirstName, userLastName),
"userEmail", userEmail, "userEmail", userEmail,
"userFirstName", userFirstName, "userFirstName", userFirstName,
"userLastName", userLastName, "userLastName", userLastName,

3
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.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -40,6 +41,7 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
private CustomerId entityCustomerId; private CustomerId entityCustomerId;
private UUID userId; private UUID userId;
private String userTitle;
private String userEmail; private String userEmail;
private String userFirstName; private String userFirstName;
private String userLastName; private String userLastName;
@ -52,6 +54,7 @@ public class EntityActionNotificationInfo implements RuleOriginatedNotificationI
"entityName", entityName, "entityName", entityName,
"actionType", actionType.name().toLowerCase(), "actionType", actionType.name().toLowerCase(),
"userId", userId.toString(), "userId", userId.toString(),
"userTitle", userTitle,
"userEmail", userEmail, "userEmail", userEmail,
"userFirstName", userFirstName, "userFirstName", userFirstName,
"userLastName", userLastName "userLastName", userLastName

24
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; package org.thingsboard.server.common.data.notification.targets.slack;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@ -24,6 +25,7 @@ import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.notification.targets.NotificationRecipient; import org.thingsboard.server.common.data.notification.targets.NotificationRecipient;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isEmpty;
@ -31,23 +33,34 @@ import static org.apache.commons.lang3.StringUtils.isEmpty;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@JsonIgnoreProperties(ignoreUnknown = true)
public class SlackConversation implements NotificationRecipient { public class SlackConversation implements NotificationRecipient {
@NotNull
private SlackConversationType type;
@NotEmpty @NotEmpty
private String id; private String id;
@NotEmpty @NotEmpty
private String title; private String name;
private String shortName;
private String wholeName; private String wholeName;
private String email; private String email;
@Override
public String getTitle() {
if (type == SlackConversationType.DIRECT) {
return StringUtils.defaultIfEmpty(wholeName, name);
} else {
return name;
}
}
@JsonIgnore @JsonIgnore
@Override @Override
public String getFirstName() { public String getFirstName() {
String firstName = StringUtils.contains(wholeName, " ") ? wholeName.split(" ")[0] : wholeName; String firstName = StringUtils.contains(wholeName, " ") ? wholeName.split(" ")[0] : wholeName;
if (isEmpty(firstName)) { if (isEmpty(firstName)) {
firstName = shortName; firstName = name;
} }
return firstName; return firstName;
} }
@ -58,4 +71,9 @@ public class SlackConversation implements NotificationRecipient {
return StringUtils.contains(wholeName, " ") ? wholeName.split(" ")[1] : null; return StringUtils.contains(wholeName, " ") ? wholeName.split(" ")[1] : null;
} }
@JsonIgnore
public String getPointer() {
return type == SlackConversationType.DIRECT ? "@" : "#";
}
} }

8
common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java

@ -15,8 +15,6 @@
*/ */
package org.thingsboard.server.common.data.util; package org.thingsboard.server.common.data.util;
import com.google.common.collect.ImmutableMap;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -63,8 +61,12 @@ public class CollectionsUtil {
return map; return map;
} }
public static <K, V> Map<K, V> unmodifiableMapOf(Object... kvs) { public static <K, V> Map<K, V> unmodifiableMapOf(Object... kvs) {
return Collections.unmodifiableMap(mapOf(kvs)); return Collections.unmodifiableMap(mapOf(kvs));
} }
public static <V> boolean emptyOrContains(Collection<V> collection, V element) {
return isEmpty(collection) || collection.contains(element);
}
} }

47
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;
}
}

3
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.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; 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.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
@ -368,7 +369,7 @@ public class HashPartitionService implements PartitionService {
} }
} }
if (routingInfo == null) { if (routingInfo == null) {
throw new RuntimeException("Tenant not found!"); throw new TenantNotFoundException(tenantId);
} }
switch (serviceType) { switch (serviceType) {
case TB_RULE_ENGINE: case TB_RULE_ENGINE:

3
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ApiUsageRecordKey; 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.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -122,6 +123,8 @@ public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, parent.getTenantId(), parent.getId()) TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, parent.getTenantId(), parent.getId())
.newByTopic(msgProducer.getDefaultTopic()); .newByTopic(msgProducer.getDefaultTopic());
msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), statsMsg.build()), null); 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) { } catch (Exception e) {
log.warn("Failed to report usage stats for tenant {}", parent.getTenantId(), e); log.warn("Failed to report usage stats for tenant {}", parent.getTenantId(), e);
} }

8
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); return notificationTargetDao.saveAndFlush(tenantId, notificationTarget);
} catch (Exception e) { } catch (Exception e) {
checkConstraintViolation(e, Map.of( 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; throw e;
} }
@ -109,7 +109,7 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
List<User> users = ((UserListFilter) usersFilter).getUsersIds().stream() List<User> users = ((UserListFilter) usersFilter).getUsersIds().stream()
.limit(pageLink.getPageSize()) .limit(pageLink.getPageSize())
.map(UserId::new).map(userId -> userService.findUserById(tenantId, userId)) .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); return new PageData<>(users, 1, users.size(), false);
} }
case CUSTOMER_USERS: { case CUSTOMER_USERS: {
@ -179,10 +179,10 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
@Override @Override
public void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id) { public void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id) {
if (notificationRequestDao.existsByTenantIdAndStatusAndTargetId(tenantId, NotificationRequestStatus.SCHEDULED, 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)) { 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()); notificationTargetDao.removeById(tenantId, id.getId());
} }

8
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java

@ -71,7 +71,7 @@ public class DefaultNotifications {
.type(NotificationType.ENTITIES_LIMIT) .type(NotificationType.ENTITIES_LIMIT)
.subject("${entityType}s limit will be reached soon for tenant ${tenantName}") .subject("${entityType}s limit will be reached soon for tenant ${tenantName}")
.text("${entityType}s usage: ${currentCount}/${limit} (${percents}%)") .text("${entityType}s usage: ${currentCount}/${limit} (${percents}%)")
.icon("warning").color("#D6D634") .icon("warning").color("#F9D916")
.rule(DefaultRule.builder() .rule(DefaultRule.builder()
.name("Entities count limit (sysadmin)") .name("Entities count limit (sysadmin)")
.triggerConfig(EntitiesLimitNotificationRuleTriggerConfig.builder() .triggerConfig(EntitiesLimitNotificationRuleTriggerConfig.builder()
@ -82,7 +82,7 @@ public class DefaultNotifications {
.build(); .build();
public static final DefaultNotification entitiesLimitForTenant = entitiesLimitForSysadmin.toBuilder() public static final DefaultNotification entitiesLimitForTenant = entitiesLimitForSysadmin.toBuilder()
.name("Entities count limit notification for tenant") .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() .rule(entitiesLimitForSysadmin.getRule().toBuilder()
.name("Entities count limit") .name("Entities count limit")
.description("Send notification to tenant admins when count of entities of some type reached 80% threshold of the 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) .type(NotificationType.API_USAGE_LIMIT)
.subject("${feature} feature will be disabled soon for tenant ${tenantName}") .subject("${feature} feature will be disabled soon for tenant ${tenantName}")
.text("Usage: ${currentValue} out of ${limit} ${unitLabel}s") .text("Usage: ${currentValue} out of ${limit} ${unitLabel}s")
.icon("warning").color("#D6D634") .icon("warning").color("#F9D916")
.rule(DefaultRule.builder() .rule(DefaultRule.builder()
.name("API feature warning (sysadmin)") .name("API feature warning (sysadmin)")
.triggerConfig(ApiUsageLimitNotificationRuleTriggerConfig.builder() .triggerConfig(ApiUsageLimitNotificationRuleTriggerConfig.builder()
@ -105,7 +105,7 @@ public class DefaultNotifications {
.build(); .build();
public static final DefaultNotification apiFeatureWarningForTenant = apiFeatureWarningForSysadmin.toBuilder() public static final DefaultNotification apiFeatureWarningForTenant = apiFeatureWarningForSysadmin.toBuilder()
.name("API feature warning notification for tenant") .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() .rule(apiFeatureWarningForSysadmin.getRule().toBuilder()
.name("API feature warning") .name("API feature warning")
.description("Send notification to tenant admins on API feature usage WARNING state") .description("Send notification to tenant admins on API feature usage WARNING state")

6
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.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; 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.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity; import org.thingsboard.server.dao.model.sql.NotificationRuleEntity;
import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity; import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity;
@ -42,7 +41,10 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
boolean existsByTenantIdAndRecipientsConfigContaining(UUID tenantId, String searchString); @Query("SELECT count(r) > 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<NotificationRuleEntity> findAllByTenantIdAndTriggerType(UUID tenantId, NotificationRuleTriggerType triggerType); List<NotificationRuleEntity> findAllByTenantIdAndTriggerType(UUID tenantId, NotificationRuleTriggerType triggerType);

2
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; void testConnection(TenantId tenantId) throws Exception;
boolean isConfigured(TenantId tenantId);
} }

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

@ -27,8 +27,6 @@ public interface SlackService {
List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversationType conversationType); List<SlackConversation> listConversations(TenantId tenantId, String token, SlackConversationType conversationType);
SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern);
String getToken(TenantId tenantId); String getToken(TenantId tenantId);
} }

11
ui-ngx/src/app/shared/models/notification.models.ts

@ -101,9 +101,10 @@ interface SlackNotificationDeliveryMethodConfig {
export interface SlackConversation { export interface SlackConversation {
id: string; id: string;
title: string; title: string;
shortName: string; name: string;
wholeName: string; wholeName: string;
email: string; email: string;
type: string;
} }
export interface NotificationRule extends Omit<BaseData<NotificationRuleId>, 'label'>{ export interface NotificationRule extends Omit<BaseData<NotificationRuleId>, 'label'>{
@ -443,7 +444,7 @@ export enum NotificationType {
ENTITIES_LIMIT = 'ENTITIES_LIMIT', ENTITIES_LIMIT = 'ENTITIES_LIMIT',
API_USAGE_LIMIT = 'API_USAGE_LIMIT', API_USAGE_LIMIT = 'API_USAGE_LIMIT',
NEW_PLATFORM_VERSION = 'NEW_PLATFORM_VERSION', NEW_PLATFORM_VERSION = 'NEW_PLATFORM_VERSION',
RULE_ENGINE = 'RULE_ENGINE' RULE_NODE = 'RULE_NODE'
} }
export const NotificationTypeIcons = new Map<NotificationType, string | null>([ export const NotificationTypeIcons = new Map<NotificationType, string | null>([
@ -543,10 +544,10 @@ export const NotificationTemplateTypeTranslateMap = new Map<NotificationType, No
helpId: 'notification/new_platform_version' helpId: 'notification/new_platform_version'
} }
], ],
[NotificationType.RULE_ENGINE, [NotificationType.RULE_NODE,
{ {
name: 'notification.template-type.rule-engine', name: 'notification.template-type.rule-node',
helpId: 'notification/rule_engine' helpId: 'notification/rule_node'
} }
] ]
]); ]);

43
ui-ngx/src/assets/help/en_US/notification/alarm.md

@ -3,35 +3,38 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `alarmType` - alarm type;
* *recipientFirstName* - first name of the recipient; * `action` - one of: 'created', 'severity changed', 'acknowledged', 'cleared', 'deleted';
* *recipientLastName* - last name of the recipient; * `alarmId` - the alarm id as uuid string;
* *alarmType* - alarm type; * `alarmSeverity` - alarm severity (lower case);
* *action* - one of: 'created', 'severity changed', 'acknowledged', 'cleared', 'deleted'; * `alarmStatus` - the alarm status;
* *alarmId* - the alarm id as uuid string; * `alarmOriginatorEntityType` - the entity type of the alarm originator, e.g. 'Device';
* *alarmSeverity* - alarm severity (lower case); * `alarmOriginatorName` - the name of the alarm originator, e.g. 'Sensor T1';
* *alarmStatus* - the alarm status; * `alarmOriginatorId` - the alarm originator entity id as uuid string;
* *alarmOriginatorEntityType* - the entity type of the alarm originator, e.g. 'Device'; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* *alarmOriginatorName* - the name of the alarm originator, e.g. 'Sensor T1'; * `recipientEmail` - email of the recipient;
* *alarmOriginatorId* - the alarm originator entity id as uuid string; * `recipientFirstName` - first name of the recipient;
* `recipientLastName` - last name of the recipient;
Parameter names must be wrapped using `${...}`. For example: `${action}`.
Parameter names must be wrapped using `${...}`. For example: `${action}`.
You may also modify the value of the parameter with one of the suffixes: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
##### Examples ##### 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 ```text
Alarm '${alarmType}' - ${action:upperCase} Alarm '${alarmType}' - ${action:upperCase}
@ -42,9 +45,10 @@ will be transformed to:
```text ```text
Alarm 'High Temperature' - CREATED Alarm 'High Temperature' - CREATED
{:copy-code}
``` ```
<br/>
The following template: The following template:
```text ```text
@ -56,7 +60,6 @@ will be transformed to:
```text ```text
Device - Sensor A Device - Sensor A
{:copy-code}
``` ```
<br> <br>

59
ui-ngx/src/assets/help/en_US/notification/alarm_assignment.md

@ -3,42 +3,47 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `alarmType` - alarm type;
* *recipientFirstName* - first name of the recipient; * `alarmId` - the alarm id as uuid string;
* *recipientLastName* - last name of the recipient; * `alarmSeverity` - alarm severity (lower case);
* *alarmType* - alarm type; * `alarmStatus` - the alarm status;
* *alarmId* - the alarm id as uuid string; * `alarmOriginatorEntityType` - the entity type of the alarm originator, e.g. 'Device';
* *alarmSeverity* - alarm severity (lower case); * `alarmOriginatorName` - the name of the alarm originator, e.g. 'Sensor T1';
* *alarmStatus* - the alarm status; * `alarmOriginatorId` - the alarm originator entity id as uuid string;
* *alarmOriginatorEntityType* - the entity type of the alarm originator, e.g. 'Device'; * `assigneeTitle` - title of the assignee;
* *alarmOriginatorName* - the name of the alarm originator, e.g. 'Sensor T1'; * `assigneeEmail` - email of the assignee;
* *alarmOriginatorId* - the alarm originator entity id as uuid string; * `assigneeFirstName` - first name of the assignee;
* *assigneeEmail* - email of the assignee; * `assigneeLastName` - last name of the assignee;
* *assigneeFirstName* - first name of the assignee; * `assigneeId` - the id of the assignee as uuid string;
* *assigneeLastName* - last name of the assignee; * `userTitle` - title of the user who made the action;
* *assigneeId* - the id of the assignee as uuid string; * `userEmail` - email 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;
* *userFirstName* - first name of the user who made the action; * `userLastName` - last name of the user who made the action;
* *userLastName* - last name of the user who made the action; * `action` - one of: 'assigned', 'unassigned';
* *action* - one of: 'assigned', 'unassigned'; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* `recipientEmail` - email of the recipient;
Parameter names must be wrapped using `${...}`. For example: `${action}`. * `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: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
##### Examples ##### 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 ```text
Alarm '${alarmType}' - ${action:upperCase} Alarm '${alarmType}' - ${action:upperCase}
@ -49,9 +54,10 @@ will be transformed to:
```text ```text
Alarm 'High Temperature' - ASSIGNED Alarm 'High Temperature' - ASSIGNED
{:copy-code}
``` ```
<br/>
The following template: The following template:
```text ```text
@ -63,7 +69,6 @@ will be transformed to:
```text ```text
Alarm 'High Temperature' (Critical) was assigned to user Alarm 'High Temperature' (Critical) was assigned to user
{:copy-code}
``` ```
<br> <br>

53
ui-ngx/src/assets/help/en_US/notification/alarm_comment.md

@ -3,40 +3,43 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `alarmType` - alarm type;
* *recipientFirstName* - first name of the recipient; * `alarmId` - the alarm id as uuid string;
* *recipientLastName* - last name of the recipient; * `alarmSeverity` - alarm severity (lower case);
* *alarmType* - alarm type; * `alarmStatus` - the alarm status;
* *alarmId* - the alarm id as uuid string; * `alarmOriginatorEntityType` - the entity type of the alarm originator, e.g. 'Device';
* *alarmSeverity* - alarm severity (lower case); * `alarmOriginatorName` - the name of the alarm originator, e.g. 'Sensor T1';
* *alarmStatus* - the alarm status; * `alarmOriginatorId` - the alarm originator entity id as uuid string;
* *alarmOriginatorEntityType* - the entity type of the alarm originator, e.g. 'Device'; * `comment` - text of the comment;
* *alarmOriginatorName* - the name of the alarm originator, e.g. 'Sensor T1'; * `action` - one of: 'added', 'updated';
* *alarmOriginatorId* - the alarm originator entity id as uuid string; * `userTitle` - title of the user who made the action;
* *comment* - text of the comment; * `userEmail` - email of the user who made the action;
* *action* - one of: 'added', 'updated'; * `userFirstName` - first name of the user who made the action;
* *userEmail* - email of the user who made the action; * `userLastName` - last name of the user who made the action;
* *userFirstName* - first name of the user who made the action; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* *userLastName* - last name of the user who made the action; * `recipientEmail` - email of the recipient;
* `recipientFirstName` - first name of the recipient;
Parameter names must be wrapped using `${...}`. For example: `${action}`. * `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: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
##### Examples ##### Examples
* Let's assume the notification about alarm with type 'High Temperature' for device 'Sensor A' was assigned to user 'John Doe'. Let's assume the notification about alarm with type 'High Temperature' for device 'Sensor A' was assigned
The following template: to user 'John Doe'. The following template:
```text ```text
Alarm '${alarmType}' - comment ${action} Alarm '${alarmType}' - comment ${action}
@ -47,9 +50,10 @@ will be transformed to:
```text ```text
Alarm 'High Temperature' - comment added Alarm 'High Temperature' - comment added
{:copy-code}
``` ```
<br/>
The following template: The following template:
```text ```text
@ -61,7 +65,6 @@ will be transformed to:
```text ```text
Alarm 'High Temperature' (Critical) was commented Alarm 'High Temperature' (Critical) was commented
{:copy-code}
``` ```
<br> <br>

40
ui-ngx/src/assets/help/en_US/notification/api_usage_limit.md

@ -3,34 +3,39 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `feature` - API feature for which the limit is applied; one of: 'Device API', 'Telemetry persistence',
* *recipientFirstName* - first name of the recipient; 'Rule Engine execution', 'JavaScript functions execution', 'Email messages', 'SMS messages', 'Alarms';
* *recipientLastName* - last name of the recipient; * `status` - one of: 'enabled', 'warning', 'disabled';
* *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'; * `unitLabel` - name of the limited unit; one of: 'message', 'data point', 'Rule Engine execution',
* *status* - one of: 'enabled', 'warning', 'disabled'; 'JavaScript execution', 'email message', 'SMS message', 'alarm';
* *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;
* *limit* - the limit on used feature units; * `currentValue` - current number of used units;
* *currentValue* - current number of used units; * `tenantId` - id of the tenant;
* *tenantId* - id of the tenant; * `tenantName` - name 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;
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. * `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: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
##### Examples ##### 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 ```text
${feature} feature - ${status:upperCase} (usage: ${currentValue} out of ${limit} ${unitLabel}s) ${feature} feature - ${status:upperCase} (usage: ${currentValue} out of ${limit} ${unitLabel}s)
@ -41,7 +46,6 @@ will be transformed to:
```text ```text
Device API feature - WARNING (usage: 8000 out of 10000 messages) Device API feature - WARNING (usage: 8000 out of 10000 messages)
{:copy-code}
``` ```
<br> <br>

39
ui-ngx/src/assets/help/en_US/notification/device_activity.md

@ -3,36 +3,36 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `deviceId` - the device id as uuid string;
* *recipientFirstName* - first name of the recipient; * `deviceName` - the device name;
* *recipientLastName* - last name of the recipient; * `deviceLabel` - the device label;
* *deviceId* - the device id as uuid string; * `deviceType` - the device type;
* *deviceName* - the device name; * `eventType` - one of: 'inactive', 'active';
* *deviceLabel* - the device label; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* *deviceType* - the device type; * `recipientEmail` - email of the recipient;
* *eventType* - one of: 'inactive', 'active'; * `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: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
##### Examples ##### 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: The following template:
Template message: ``
```text ```text
Device '${deviceName}' inactive Device '${deviceName}' inactive
{:copy-code} {:copy-code}
@ -42,11 +42,10 @@ will be transformed to:
```text ```text
Device 'Sensor T1' inactive Device 'Sensor T1' inactive
{:copy-code}
``` ```
<br/>
<br>
The following template: The following template:
```text ```text
@ -58,7 +57,7 @@ will be transformed to:
```text ```text
Thermometer 'Sensor T1' became inactive Thermometer 'Sensor T1' became inactive
{:copy-code}
``` ```
<br> <br>
<br> <br>

36
ui-ngx/src/assets/help/en_US/notification/entities_limit.md

@ -3,33 +3,36 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `entityType` - one of: 'Device', 'Asset', 'User', etc.;
* *recipientFirstName* - first name of the recipient; * `currentCount` - the current count of entities;
* *recipientLastName* - last name of the recipient; * `limit` - the limit on number of entities;
* *entityType* - one of: 'Device', 'Asset', 'User', etc.; * `percents` - the percent from the notification rule configuration;
* *currentCount* - the current count of entities; * `tenantId` - id of the tenant;
* *limit* - the limit on number of entities; * `tenantName` - name of the tenant;
* *percents* - the percent from the notification rule configuration; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* *tenantId* - id of the tenant; * `recipientEmail` - email of the recipient;
* *tenantName* - name of the tenant; * `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: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
##### Examples ##### 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 ```text
${entityType:capitalize}s usage: ${currentCount}/${limit} (${percents}%) ${entityType:capitalize}s usage: ${currentCount}/${limit} (${percents}%)
@ -40,7 +43,6 @@ will be transformed to:
```text ```text
Devices usage: 400/500 (80%) Devices usage: 400/500 (80%)
{:copy-code}
``` ```
<br> <br>

48
ui-ngx/src/assets/help/en_US/notification/entity_action.md

@ -3,35 +3,38 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `entityType` - the entity type, e.g. 'Device';
* *recipientFirstName* - first name of the recipient; * `entityId` - the entity id as uuid string;
* *recipientLastName* - last name of the recipient; * `entityName` - the name of the entity;
* *entityType* - the entity type, e.g. 'Device'; * `actionType` - one of: 'added', 'updated', 'deleted';
* *entityId* - the entity id as uuid string; * `userId` - id of the user who made the action;
* *entityName* - the name of the entity; * `userTitle` - title of the user who made the action;
* *actionType* - one of: 'added', 'updated', 'deleted'; * `userEmail` - email of the user who made the action;
* *userId* - id of the user who made the action; * `userFirstName` - first name of the user who made the action;
* *userEmail* - email of the user who made the action; * `userLastName` - last name of the user who made the action;
* *userFirstName* - first name of the user who made the action; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* *userLastName* - last name of the user who made the action; * `recipientEmail` - email of the recipient;
* `recipientFirstName` - first name of the recipient;
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. * `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: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
##### Examples ##### 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: The following template:
```text ```text
@ -43,22 +46,21 @@ will be transformed to:
```text ```text
Device was added! Device was added!
{:copy-code}
``` ```
<br> <br/>
The following template: The following template:
```text ```text
${entityType} '${entityName}' was ${actionType} by user '${originatorUserName}'! ${entityType} '${entityName}' was ${actionType} by user ${userEmail}
{:copy-code} {:copy-code}
``` ```
will be transformed to: will be transformed to:
```text ```text
Device 'T1' was added by user 'john.doe@gmail.com'! Device 'T1' was added by user john.doe@gmail.com
{:copy-code}
``` ```
<br> <br>

19
ui-ngx/src/assets/help/en_US/notification/general.md

@ -3,21 +3,23 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* *recipientFirstName* - first name of the recipient; * `recipientEmail` - email of the recipient;
* *recipientLastName* - last name 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: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
@ -34,7 +36,6 @@ will be transformed to:
```text ```text
Hi, John! Hi, John!
{:copy-code}
``` ```
<br> <br>

31
ui-ngx/src/assets/help/en_US/notification/new_platform_version.md

@ -3,26 +3,28 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `latestVersion` - the latest platform version available;
* *recipientFirstName* - first name of the recipient; * `latestVersionReleaseNotesUrl` - release notes link for latest version;
* *recipientLastName* - last name of the recipient; * `upgradeInstructionsUrl` - upgrade instructions link for latest version;
* *latestVersion* - the latest platform version available; * `currentVersion` - the current platform version
* *latestVersionReleaseNotesUrl* - release notes link for latest version; * `currentVersionReleaseNotesUrl` - release notes link for current version;
* *upgradeInstructionsUrl* - upgrade instructions link for latest version; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* *currentVersion* - the current platform version * `recipientEmail` - email of the recipient;
* *currentVersionReleaseNotesUrl* - release notes link for current version; * `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: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
@ -39,7 +41,6 @@ will be transformed to:
```text ```text
New version 3.5.0 is available. Current version is 3.4.4 New version 3.5.0 is available. Current version is 3.4.4
{:copy-code}
``` ```
<br> <br>

54
ui-ngx/src/assets/help/en_US/notification/rule_engine.md

@ -1,54 +0,0 @@
#### Rule engine notification templatization
<div class="divider"></div>
<br/>
Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type.
See the available types and parameters below:
Available template parameters:
* values from the incoming message metadata 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}`
<div class="divider"></div>
##### Examples
Let's assume the incoming message to Rule node has the following data:
```json
{
"building_1": {
"temperature": 24
}
}
```
The following template:
```text
Building 1: temperature is ${building_1.temperature}
{:copy-code}
```
will be transformed to:
```text
Building 1: temperature is 24
```
<br>
<br>

37
ui-ngx/src/assets/help/en_US/notification/rule_engine_lifecycle_event.md

@ -3,29 +3,31 @@
<div class="divider"></div> <div class="divider"></div>
<br/> <br/>
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: See the available types and parameters below:
Available template parameters: Available template parameters:
* *recipientEmail* - email of the recipient; * `componentType` - one of: 'Rule chain', 'Rule node';
* *recipientFirstName* - first name of the recipient; * `componentId` - the component id as uuid string;
* *recipientLastName* - last name of the recipient; * `componentName` - the rule chain or rule node name;
* *componentType* - one of: 'Rule chain', 'Rule node'; * `ruleChainId` - the rule chain id as uuid string;
* *componentId* - the component id as uuid string; * `ruleChainName` - the rule chain name;
* *componentName* - the rule chain or rule node name; * `eventType` - one of: 'started', 'updated', 'stopped';
* *ruleChainId* - the rule chain id as uuid string; * `action` - one of: 'start', 'update', 'stop';
* *ruleChainName* - the rule chain name; * `error` - the error text;
* *eventType* - one of: 'started', 'updated', 'stopped'; * `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* *action* - one of: 'start', 'update', 'stop'; * `recipientEmail` - email of the recipient;
* *error* - the error text; * `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: You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}` * `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` * `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}` * `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div> <div class="divider"></div>
@ -42,7 +44,6 @@ will be transformed to:
```text ```text
Rule node 'Export to Kafka' - start failure:<br/>Connection refused! Rule node 'Export to Kafka' - start failure:<br/>Connection refused!
{:copy-code}
``` ```
<br> <br>

57
ui-ngx/src/assets/help/en_US/notification/rule_node.md

@ -0,0 +1,57 @@
#### Rule engine notification templatization
<div class="divider"></div>
<br/>
Notification subject and message fields support templatization.
The list of available templatization parameters depends on the template type.
See the available types and parameters below:
Available template parameters:
* values from the incoming message metadata 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}`
<div class="divider"></div>
##### Examples
Let's assume the incoming message to Rule node has the following data:
```json
{
"building_1": {
"temperature": 24
}
}
```
The following template:
```text
Building 1: temperature is ${building_1.temperature}
{:copy-code}
```
will be transformed to:
```text
Building 1: temperature is 24
```
<br>
<br>

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2960,7 +2960,7 @@
"entity-action": "Entity action", "entity-action": "Entity action",
"general": "General", "general": "General",
"rule-engine-lifecycle-event": "Rule engine lifecycle event", "rule-engine-lifecycle-event": "Rule engine lifecycle event",
"rule-engine": "Rule engine", "rule-node": "Rule node",
"new-platform-version": "New platform version" "new-platform-version": "New platform version"
}, },
"templates": "Templates", "templates": "Templates",

Loading…
Cancel
Save