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

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

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

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

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) {
return Map.of(
"recipientTitle", recipient.getTitle(),
"recipientEmail", Strings.nullToEmpty(recipient.getEmail()),
"recipientFirstName", Strings.nullToEmpty(recipient.getFirstName()),
"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
public void check(TenantId tenantId) throws Exception {
try {
mailService.testConnection(tenantId);
} catch (Exception e) {
throw new RuntimeException("Mail server is not available");
if (!mailService.isConfigured(tenantId)) {
throw new RuntimeException("Mail server is not configured");
}
}

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

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

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.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmStatusFilter;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo;
@ -25,11 +26,10 @@ import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotific
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.ClearRule;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.msg.notification.trigger.AlarmTrigger;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service
public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<AlarmTrigger, AlarmNotificationRuleTriggerConfig> {
@ -85,11 +85,11 @@ public class AlarmTriggerProcessor implements NotificationRuleTriggerProcessor<A
}
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) {
return isEmpty(triggerConfig.getAlarmTypes()) || triggerConfig.getAlarmTypes().contains(alarm.getType());
return emptyOrContains(triggerConfig.getAlarmTypes(), alarm.getType());
}
@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.dao.tenant.TenantService;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service
@RequiredArgsConstructor
@ -34,8 +34,8 @@ public class ApiUsageLimitTriggerProcessor implements NotificationRuleTriggerPro
@Override
public boolean matchesFilter(ApiUsageLimitTrigger trigger, ApiUsageLimitNotificationRuleTriggerConfig triggerConfig) {
return (isEmpty(triggerConfig.getApiFeatures()) || triggerConfig.getApiFeatures().contains(trigger.getState().getApiFeature())) &&
(isEmpty(triggerConfig.getNotifyOn()) || triggerConfig.getNotifyOn().contains(trigger.getStatus()));
return emptyOrContains(triggerConfig.getApiFeatures(), trigger.getState().getApiFeature()) &&
emptyOrContains(triggerConfig.getNotifyOn(), trigger.getStatus());
}
@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;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasCustomerId;
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.RuleOriginatedNotificationInfo;
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.msg.TbMsg;
import org.thingsboard.server.common.msg.notification.trigger.RuleEngineMsgTrigger;
import org.thingsboard.server.common.msg.notification.trigger.EntityActionTrigger;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.apache.commons.collections.CollectionUtils.isEmpty;
import static org.thingsboard.server.common.data.util.CollectionsUtil.emptyOrContains;
@Service
public class EntityActionTriggerProcessor implements RuleEngineMsgNotificationRuleTriggerProcessor<EntityActionNotificationRuleTriggerConfig> {
public class EntityActionTriggerProcessor implements NotificationRuleTriggerProcessor<EntityActionTrigger, EntityActionNotificationRuleTriggerConfig> {
@Override
public boolean matchesFilter(RuleEngineMsgTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) {
String msgType = trigger.getMsg().getType();
if (msgType.equals(DataConstants.ENTITY_CREATED)) {
if (!triggerConfig.isCreated()) {
return false;
}
} else if (msgType.equals(DataConstants.ENTITY_UPDATED)) {
if (!triggerConfig.isUpdated()) {
return false;
}
} else if (msgType.equals(DataConstants.ENTITY_DELETED)) {
if (!triggerConfig.isDeleted()) {
return false;
}
} else {
return false;
}
return isEmpty(triggerConfig.getEntityTypes()) || triggerConfig.getEntityTypes().contains(getEntityType(trigger.getMsg()));
public boolean matchesFilter(EntityActionTrigger trigger, EntityActionNotificationRuleTriggerConfig triggerConfig) {
return ((trigger.getActionType() == ActionType.ADDED && triggerConfig.isCreated())
|| (trigger.getActionType() == ActionType.UPDATED && triggerConfig.isUpdated())
|| (trigger.getActionType() == ActionType.DELETED && triggerConfig.isDeleted()))
&& emptyOrContains(triggerConfig.getEntityTypes(), trigger.getEntityId().getEntityType());
}
@Override
public RuleOriginatedNotificationInfo constructNotificationInfo(RuleEngineMsgTrigger trigger) {
TbMsg msg = trigger.getMsg();
String msgType = msg.getType();
ActionType actionType = msgType.equals(DataConstants.ENTITY_CREATED) ? ActionType.ADDED :
msgType.equals(DataConstants.ENTITY_UPDATED) ? ActionType.UPDATED :
msgType.equals(DataConstants.ENTITY_DELETED) ? ActionType.DELETED : null;
public RuleOriginatedNotificationInfo constructNotificationInfo(EntityActionTrigger trigger) {
return EntityActionNotificationInfo.builder()
.entityId(msg.getOriginator())
.entityName(msg.getMetaData().getValue("entityName"))
.actionType(actionType)
.userId(UUID.fromString(msg.getMetaData().getValue("userId")))
.userEmail(trigger.getMsg().getMetaData().getValue("userEmail"))
.userFirstName(trigger.getMsg().getMetaData().getValue("userFirstName"))
.userLastName(trigger.getMsg().getMetaData().getValue("userLastName"))
.entityCustomerId(msg.getCustomerId())
.entityId(trigger.getEntityId())
.entityName(trigger.getEntity().getName())
.actionType(trigger.getActionType())
.userId(trigger.getUser().getUuidId())
.userTitle(trigger.getUser().getTitle())
.userEmail(trigger.getUser().getEmail())
.userFirstName(trigger.getUser().getFirstName())
.userLastName(trigger.getUser().getLastName())
.entityCustomerId(trigger.getEntity() instanceof HasCustomerId ?
((HasCustomerId) trigger.getEntity()).getCustomerId() :
trigger.getUser().getCustomerId())
.build();
}
private static EntityType getEntityType(TbMsg msg) {
return Optional.ofNullable(msg.getMetaData().getValue("entityType"))
.map(EntityType::valueOf).orElse(null);
}
@Override
public NotificationRuleTriggerType getTriggerType() {
return NotificationRuleTriggerType.ENTITY_ACTION;
}
@Override
public Set<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 org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@Slf4j
@Service
@ -46,7 +46,7 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService
if (tenantProfile != null) {
return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbRuleEngine());
} else {
throw new RuntimeException("Tenant not found!");
throw new TenantNotFoundException(tenantId);
}
}
}

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

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.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@ -453,6 +454,9 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
updateInactivityStateIfExpired(ts, deviceId, stateData);
} catch (Exception e) {
log.warn("[{}] Failed to update inactivity state", deviceId, e);
if (e instanceof TenantNotFoundException) {
continue;
}
}
Pair<AtomicInteger, AtomicInteger> tenantDevicesActivity = devicesActivity.computeIfAbsent(stateData.getTenantId(),
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.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
@ -52,6 +56,7 @@ import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.DaoUtil;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -195,6 +200,32 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
new PageLink(limit, 0), unreadOnly).getData();
}
protected NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) {
NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, NotificationDeliveryMethod.WEB);
NotificationRule rule = new NotificationRule();
rule.setName(triggerConfig.getTriggerType() + " [" + Arrays.toString(targets) + "]");
rule.setTemplateId(template.getId());
rule.setTriggerType(triggerConfig.getTriggerType());
rule.setTriggerConfig(triggerConfig);
DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig();
recipientsConfig.setTriggerType(triggerConfig.getTriggerType());
recipientsConfig.setTargets(DaoUtil.toUUIDs(List.of(targets)));
rule.setRecipientsConfig(recipientsConfig);
return saveNotificationRule(rule);
}
protected NotificationRule saveNotificationRule(NotificationRule notificationRule) {
return doPost("/api/notification/rule", notificationRule, NotificationRule.class);
}
protected PageData<NotificationRuleInfo> findNotificationRules() throws Exception {
PageLink pageLink = new PageLink(10);
return doGetTypedWithPageLink("/api/notification/rules?", new TypeReference<PageData<NotificationRuleInfo>>() {}, pageLink);
}
@Override
protected NotificationApiWsClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException {
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();
slackTargetConfig.setConversationType(SlackConversationType.DIRECT);
SlackConversation slackConversation = new SlackConversation();
slackConversation.setType(SlackConversationType.DIRECT);
slackConversation.setId("U1234567");
slackConversation.setTitle("@jdoe (John Doe)");
slackConversation.setName("jdoe");
slackConversation.setWholeName("John Doe");
slackTargetConfig.setConversation(slackConversation);
slackTarget.setConfiguration(slackTargetConfig);
slackTarget = saveNotificationTarget(slackTarget);
recipients.add(slackConversation.getTitle());
recipients.add("@" + slackConversation.getWholeName());
NotificationTemplate notificationTemplate = new NotificationTemplate();
notificationTemplate.setNotificationType(NotificationType.GENERAL);
@ -500,8 +501,9 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
notificationTarget.setName(conversationName + " in Slack");
SlackNotificationTargetConfig targetConfig = new SlackNotificationTargetConfig();
targetConfig.setConversation(SlackConversation.builder()
.type(SlackConversationType.DIRECT)
.id(conversationId)
.title(conversationName)
.name(conversationName)
.build());
notificationTarget.setConfiguration(targetConfig);
notificationTarget = saveNotificationTarget(notificationTarget);

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

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.service.notification;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import org.junit.Before;
@ -43,7 +42,6 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.Notification;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
@ -59,7 +57,6 @@ import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotific
import org.thingsboard.server.common.data.notification.rule.trigger.AlarmNotificationRuleTriggerConfig.AlarmAction;
import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimitNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -73,7 +70,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.notification.NotificationRequestService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DaoSqlTest;
@ -84,7 +80,6 @@ import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileServ
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -436,7 +431,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
String name = "device " + i;
createDevice(name, name);
}
await().atMost(5, TimeUnit.SECONDS)
await().atMost(30, TimeUnit.SECONDS)
.untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(notificationRequestsLimit);
});
@ -474,21 +469,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
}, (notification, r) -> check.accept(notification));
}
private NotificationRule createNotificationRule(NotificationRuleTriggerConfig triggerConfig, String subject, String text, NotificationTargetId... targets) {
NotificationTemplate template = createNotificationTemplate(NotificationType.valueOf(triggerConfig.getTriggerType().toString()), subject, text, NotificationDeliveryMethod.WEB);
NotificationRule rule = new NotificationRule();
rule.setName(triggerConfig.getTriggerType() + " [" + Arrays.toString(targets) + "]");
rule.setTemplateId(template.getId());
rule.setTriggerType(triggerConfig.getTriggerType());
rule.setTriggerConfig(triggerConfig);
DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig();
recipientsConfig.setTriggerType(triggerConfig.getTriggerType());
recipientsConfig.setTargets(DaoUtil.toUUIDs(List.of(targets)));
rule.setRecipientsConfig(recipientsConfig);
return saveNotificationRule(rule);
private PageData<NotificationRequestInfo> findNotificationRequests(EntityType originatorType) {
return notificationRequestService.findNotificationRequestsInfosByTenantIdAndOriginatorType(tenantId, originatorType, new PageLink(100));
}
private DeviceProfile createDeviceProfileWithAlarmRules(String alarmType) {
@ -526,17 +508,4 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
return deviceProfile;
}
private NotificationRule saveNotificationRule(NotificationRule notificationRule) {
return doPost("/api/notification/rule", notificationRule, NotificationRule.class);
}
private PageData<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.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
@ -30,7 +32,6 @@ import org.thingsboard.server.common.data.notification.targets.platform.Platform
import org.thingsboard.server.common.data.notification.targets.platform.UserListFilter;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.notification.NotificationTargetDao;
import org.thingsboard.server.dao.service.DaoSqlTest;
@ -41,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class NotificationTargetApiTest extends AbstractControllerTest {
public class NotificationTargetApiTest extends AbstractNotificationApiTest {
@Autowired
private NotificationTargetDao notificationTargetDao;
@ -148,6 +149,26 @@ public class NotificationTargetApiTest extends AbstractControllerTest {
assertThat(notificationTargetDao.findByTenantIdAndPageLink(differentTenantId, new PageLink(10)).getData()).isEmpty();
}
@Test
public void whenDeletingTargetUsedByRule_thenReturnError() throws Exception {
NotificationTarget target = createNotificationTarget(tenantAdminUserId);
createNotificationRule(new EntityActionNotificationRuleTriggerConfig(), "Test", "Test", target.getId());
String error = getErrorMessage(doDelete("/api/notification/target/" + target.getId())
.andExpect(status().isBadRequest()));
assertThat(error).containsIgnoringCase("used in notification rule");
}
@Test
public void whenDeletingTargetUsedByScheduledNotificationRequest_thenReturnError() throws Exception {
NotificationTarget target = createNotificationTarget(tenantAdminUserId);
submitNotificationRequest(target.getId(), "Test", 100, NotificationDeliveryMethod.WEB);
String error = getErrorMessage(doDelete("/api/notification/target/" + target.getId())
.andExpect(status().isBadRequest()));
assertThat(error).containsIgnoringCase("referenced by scheduled notification request");
}
private String saveAndGetError(NotificationTarget notificationTarget, ResultMatcher statusMatcher) throws Exception {
return getErrorMessage(save(notificationTarget, statusMatcher));
}

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

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

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

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

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

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

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;
import com.google.common.collect.ImmutableMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -63,8 +61,12 @@ public class CollectionsUtil {
return map;
}
public static <K, V> Map<K, V> unmodifiableMapOf(Object... kvs) {
public static <K, V> Map<K, V> unmodifiableMapOf(Object... 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.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
@ -368,7 +369,7 @@ public class HashPartitionService implements PartitionService {
}
}
if (routingInfo == null) {
throw new RuntimeException("Tenant not found!");
throw new TenantNotFoundException(tenantId);
}
switch (serviceType) {
case TB_RULE_ENGINE:

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

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);
} catch (Exception e) {
checkConstraintViolation(e, Map.of(
"uq_notification_target_name", "Notification target with such name already exists"
"uq_notification_target_name", "Recipients group with such name already exists"
));
throw e;
}
@ -109,7 +109,7 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
List<User> users = ((UserListFilter) usersFilter).getUsersIds().stream()
.limit(pageLink.getPageSize())
.map(UserId::new).map(userId -> userService.findUserById(tenantId, userId))
.collect(Collectors.toList());
.filter(Objects::nonNull).collect(Collectors.toList());
return new PageData<>(users, 1, users.size(), false);
}
case CUSTOMER_USERS: {
@ -179,10 +179,10 @@ public class DefaultNotificationTargetService extends AbstractEntityService impl
@Override
public void deleteNotificationTargetById(TenantId tenantId, NotificationTargetId id) {
if (notificationRequestDao.existsByTenantIdAndStatusAndTargetId(tenantId, NotificationRequestStatus.SCHEDULED, id)) {
throw new IllegalArgumentException("Notification target is referenced by scheduled notification request");
throw new IllegalArgumentException("Recipients group is referenced by scheduled notification request");
}
if (notificationRuleDao.existsByTenantIdAndTargetId(tenantId, id)) {
throw new IllegalArgumentException("Notification target is being used in notification rule");
throw new IllegalArgumentException("Recipients group is being used in notification rule");
}
notificationTargetDao.removeById(tenantId, id.getId());
}

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

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

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.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType;
import org.thingsboard.server.dao.model.sql.NotificationRuleEntity;
import org.thingsboard.server.dao.model.sql.NotificationRuleInfoEntity;
@ -42,7 +41,10 @@ public interface NotificationRuleRepository extends JpaRepository<NotificationRu
@Param("searchText") String searchText,
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);

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;
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);
SlackConversation findConversation(TenantId tenantId, String token, SlackConversationType conversationType, String namePattern);
String getToken(TenantId tenantId);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -3,26 +3,28 @@
<div class="divider"></div>
<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:
Available template parameters:
* *recipientEmail* - email of the recipient;
* *recipientFirstName* - first name of the recipient;
* *recipientLastName* - last name of the recipient;
* *latestVersion* - the latest platform version available;
* *latestVersionReleaseNotesUrl* - release notes link for latest version;
* *upgradeInstructionsUrl* - upgrade instructions link for latest version;
* *currentVersion* - the current platform version
* *currentVersionReleaseNotesUrl* - release notes link for current version;
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`.
* `latestVersion` - the latest platform version available;
* `latestVersionReleaseNotesUrl` - release notes link for latest version;
* `upgradeInstructionsUrl` - upgrade instructions link for latest version;
* `currentVersion` - the current platform version
* `currentVersionReleaseNotesUrl` - release notes link for current version;
* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* `recipientEmail` - email of the recipient;
* `recipientFirstName` - first name of the recipient;
* `recipientLastName` - last name of the recipient;
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`.
You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}`
* `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div>
@ -39,7 +41,6 @@ will be transformed to:
```text
New version 3.5.0 is available. Current version is 3.4.4
{:copy-code}
```
<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>
<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:
Available template parameters:
* *recipientEmail* - email of the recipient;
* *recipientFirstName* - first name of the recipient;
* *recipientLastName* - last name of the recipient;
* *componentType* - one of: 'Rule chain', 'Rule node';
* *componentId* - the component id as uuid string;
* *componentName* - the rule chain or rule node name;
* *ruleChainId* - the rule chain id as uuid string;
* *ruleChainName* - the rule chain name;
* *eventType* - one of: 'started', 'updated', 'stopped';
* *action* - one of: 'start', 'update', 'stop';
* *error* - the error text;
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`.
* `componentType` - one of: 'Rule chain', 'Rule node';
* `componentId` - the component id as uuid string;
* `componentName` - the rule chain or rule node name;
* `ruleChainId` - the rule chain id as uuid string;
* `ruleChainName` - the rule chain name;
* `eventType` - one of: 'started', 'updated', 'stopped';
* `action` - one of: 'start', 'update', 'stop';
* `error` - the error text;
* `recipientTitle` - title of the recipient (first and last name if specified, email otherwise);
* `recipientEmail` - email of the recipient;
* `recipientFirstName` - first name of the recipient;
* `recipientLastName` - last name of the recipient;
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`.
You may also modify the value of the parameter with one of the suffixes:
* `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}`
* `upperCase`, for example - `${recipientFirstName:upperCase}`
* `lowerCase`, for example - `${recipientFirstName:lowerCase}`
* `capitalize`, for example - `${recipientFirstName:capitalize}`
<div class="divider"></div>
@ -42,7 +44,6 @@ will be transformed to:
```text
Rule node 'Export to Kafka' - start failure:<br/>Connection refused!
{:copy-code}
```
<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",
"general": "General",
"rule-engine-lifecycle-event": "Rule engine lifecycle event",
"rule-engine": "Rule engine",
"rule-node": "Rule node",
"new-platform-version": "New platform version"
},
"templates": "Templates",

Loading…
Cancel
Save