Browse Source

Merge branch 'develop/3.5' of github.com:thingsboard/thingsboard into feature/home-page

feature/home-page
Igor Kulikov 3 years ago
parent
commit
a736f177ef
  1. 13
      application/src/main/data/upgrade/3.4.4/schema_update.sql
  2. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  3. 114
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  4. 15
      application/src/main/java/org/thingsboard/server/controller/NotificationController.java
  5. 33
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  6. 1
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  7. 8
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  9. 5
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  10. 1
      application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingContext.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/notification/channels/EmailNotificationChannel.java
  12. 11
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmAssignmentTriggerProcessor.java
  13. 11
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmCommentTriggerProcessor.java
  14. 8
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/AlarmTriggerProcessor.java
  15. 6
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/ApiUsageLimitTriggerProcessor.java
  16. 73
      application/src/main/java/org/thingsboard/server/service/notification/rule/trigger/EntityActionTriggerProcessor.java
  17. 6
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
  18. 23
      application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java
  19. 4
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  20. 19
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  21. 31
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  22. 8
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  23. 37
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  24. 25
      application/src/test/java/org/thingsboard/server/service/notification/NotificationTargetApiTest.java
  25. 13
      application/src/test/java/org/thingsboard/server/service/stats/DevicesStatisticsTest.java
  26. 4
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  27. 6
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  28. 4
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  29. 4
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java
  30. 31
      common/data/src/main/java/org/thingsboard/server/common/data/exception/TenantNotFoundException.java
  31. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationType.java
  32. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmAssignmentNotificationInfo.java
  33. 2
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/AlarmCommentNotificationInfo.java
  34. 3
      common/data/src/main/java/org/thingsboard/server/common/data/notification/info/EntityActionNotificationInfo.java
  35. 24
      common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/slack/SlackConversation.java
  36. 29
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentClusteringMode.java
  37. 10
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java
  38. 5
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java
  39. 8
      common/data/src/main/java/org/thingsboard/server/common/data/util/CollectionsUtil.java
  40. 47
      common/message/src/main/java/org/thingsboard/server/common/msg/notification/trigger/EntityActionTrigger.java
  41. 3
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  42. 3
      common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java
  43. 74
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  44. 100
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
  45. 4
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  46. 2
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  47. 7
      dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java
  48. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java
  49. 8
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java
  50. 8
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java
  51. 29
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  52. 4
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  53. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java
  54. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/component/SqlComponentDescriptorInsertRepository.java
  55. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRuleRepository.java
  56. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  57. 4
      dao/src/main/resources/sql/schema-entities.sql
  58. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractBasePage.java
  59. 67
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java
  60. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/OtherPageElements.java
  61. 4
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/OtherPageElementsHelper.java
  62. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/RuleChainsPageHelper.java
  63. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/CreateAssetProfileImportTest.java
  64. 8
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/DeleteAssetProfileTest.java
  65. 8
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/DeleteCustomerTest.java
  66. 2
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/CreateDeviceProfileImportTest.java
  67. 8
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/DeleteDeviceProfileTest.java
  68. 96
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/OpenRuleChainTest.java
  69. 49
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/AbstractRuleChainTest.java
  70. 78
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/CreateRuleChainImportTest.java
  71. 97
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/CreateRuleChainTest.java
  72. 80
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/DeleteRuleChainTest.java
  73. 44
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/DeleteSeveralRuleChainsTest.java
  74. 36
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/MakeRuleChainRootTest.java
  75. 66
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/OpenRuleChainTest.java
  76. 98
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/RuleChainEditMenuTest.java
  77. 41
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/SearchRuleChainTest.java
  78. 68
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/SortByNameTest.java
  79. 55
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/SortByTimeTest.java
  80. 7
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/DataProviderCredential.java
  81. 7
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/EntityPrototypes.java
  82. 2
      msa/black-box-tests/src/test/resources/all.xml
  83. 2
      msa/black-box-tests/src/test/resources/smokesRuleChain.xml
  84. 2
      msa/black-box-tests/src/test/resources/uiTests.xml
  85. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java
  86. 3
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
  87. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java
  88. 14
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java
  89. 10
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java
  90. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java
  91. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java
  92. 13
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java
  93. 1
      ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts
  94. 13
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html
  95. 22
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.scss
  96. 6
      ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts
  97. 13
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
  98. 8
      ui-ngx/src/app/shared/models/audit-log.models.ts
  99. 7
      ui-ngx/src/app/shared/models/component-descriptor.models.ts
  100. 11
      ui-ngx/src/app/shared/models/notification.models.ts

13
application/src/main/data/upgrade/3.4.4/schema_update.sql

@ -614,3 +614,16 @@ END
$$;
-- TTL DROP PARTITIONS FUNCTIONS UPDATE END
-- RULE NODE SINGLETON MODE SUPPORT
ALTER TABLE rule_node ADD COLUMN IF NOT EXISTS singleton_mode bool DEFAULT false;
UPDATE rule_node SET singleton_mode = true WHERE type IN ('org.thingsboard.rule.engine.mqtt.azure.TbAzureIotHubNode', 'org.thingsboard.rule.engine.mqtt.TbMqttNode');
ALTER TABLE component_descriptor ADD COLUMN IF NOT EXISTS clustering_mode varchar(255) DEFAULT 'ENABLED';
UPDATE component_descriptor SET clustering_mode = 'USER_PREFERENCE' WHERE clazz = 'org.thingsboard.rule.engine.mqtt.TbMqttNode';
UPDATE component_descriptor SET clustering_mode = 'SINGLETON' WHERE clazz = 'org.thingsboard.rule.engine.mqtt.azure.TbAzureIotHubNode';

5
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -87,6 +87,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.discovery.DiscoveryService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.notification.NotificationRuleProcessor;
@ -179,6 +180,10 @@ public class ActorSystemContext {
@Setter
private ComponentDiscoveryService componentService;
@Autowired
@Getter
private DiscoveryService discoveryService;
@Autowired
@Getter
private DataDecodingEncodingService encodingService;

114
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java

@ -31,7 +31,10 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.RuleNodeException;
import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.gen.transport.TransportProtos;
/**
* @author Andrew Shvayka
@ -39,11 +42,10 @@ import org.thingsboard.server.common.stats.TbApiUsageReportClient;
public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNodeId> {
private final String ruleChainName;
private final TbActorRef self;
private final TbApiUsageReportClient apiUsageClient;
private final DefaultTbContext defaultCtx;
private RuleNode ruleNode;
private TbNode tbNode;
private DefaultTbContext defaultCtx;
private RuleNodeInfo info;
RuleNodeActorMessageProcessor(TenantId tenantId, String ruleChainName, RuleNodeId ruleNodeId, ActorSystemContext systemContext
@ -51,7 +53,6 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
super(systemContext, tenantId, ruleNodeId);
this.apiUsageClient = systemContext.getApiUsageClient();
this.ruleChainName = ruleChainName;
this.self = self;
this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
this.defaultCtx = new DefaultTbContext(systemContext, ruleChainName, new RuleNodeCtx(tenantId, parent, self, ruleNode));
this.info = new RuleNodeInfo(ruleNodeId, ruleChainName, ruleNode != null ? ruleNode.getName() : "Unknown");
@ -59,29 +60,36 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
@Override
public void start(TbActorCtx context) throws Exception {
tbNode = initComponent(ruleNode);
if (tbNode != null) {
state = ComponentLifecycleState.ACTIVE;
if (isMyNodePartition()) {
tbNode = initComponent(ruleNode);
if (tbNode != null) {
state = ComponentLifecycleState.ACTIVE;
}
}
}
@Override
public void onUpdate(TbActorCtx context) throws Exception {
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
this.info = new RuleNodeInfo(entityId, ruleChainName, newRuleNode != null ? newRuleNode.getName() : "Unknown");
boolean restartRequired = state != ComponentLifecycleState.ACTIVE ||
!(ruleNode.getType().equals(newRuleNode.getType()) && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
this.ruleNode = newRuleNode;
this.defaultCtx.updateSelf(newRuleNode);
if (restartRequired) {
if (tbNode != null) {
tbNode.destroy();
}
try {
start(context);
} catch (Exception e) {
throw new TbRuleNodeUpdateException("Failed to update rule node", e);
if (isMyNodePartition()) {
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
this.info = new RuleNodeInfo(entityId, ruleChainName, newRuleNode != null ? newRuleNode.getName() : "Unknown");
boolean restartRequired = state != ComponentLifecycleState.ACTIVE ||
!(ruleNode.getType().equals(newRuleNode.getType()) && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
this.ruleNode = newRuleNode;
this.defaultCtx.updateSelf(newRuleNode);
if (restartRequired) {
if (tbNode != null) {
tbNode.destroy();
}
try {
start(context);
} catch (Exception e) {
throw new TbRuleNodeUpdateException("Failed to update rule node", e);
}
}
} else if (tbNode != null) {
stop(null);
tbNode = null;
}
}
@ -94,9 +102,16 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
@Override
public void onPartitionChangeMsg(PartitionChangeMsg msg) {
public void onPartitionChangeMsg(PartitionChangeMsg msg) throws Exception {
if (tbNode != null) {
tbNode.onPartitionChangeMsg(defaultCtx, msg);
if (!isMyNodePartition()) {
stop(null);
tbNode = null;
} else {
tbNode.onPartitionChangeMsg(defaultCtx, msg);
}
} else if (isMyNodePartition()) {
start(null);
}
}
@ -121,23 +136,27 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
msg.getMsg().getCallback().onProcessingStart(info);
checkComponentStateActive(msg.getMsg());
TbMsg tbMsg = msg.getMsg();
int ruleNodeCount = tbMsg.getAndIncrementRuleNodeCounter();
int maxRuleNodeExecutionsPerMessage = getTenantProfileConfiguration().getMaxRuleNodeExecsPerMessage();
if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) {
apiUsageClient.report(tenantId, tbMsg.getCustomerId(), ApiUsageRecordKey.RE_EXEC_COUNT);
if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());
}
try {
tbNode.onMsg(msg.getCtx(), msg.getMsg());
} catch (Exception e) {
msg.getCtx().tellFailure(msg.getMsg(), e);
}
if (!isMyNodePartition()) {
putToNodePartition(msg.getMsg());
} else {
tbMsg.getCallback().onFailure(new RuleNodeException("Message is processed by more then " + maxRuleNodeExecutionsPerMessage + " rule nodes!", ruleChainName, ruleNode));
msg.getMsg().getCallback().onProcessingStart(info);
checkComponentStateActive(msg.getMsg());
TbMsg tbMsg = msg.getMsg();
int ruleNodeCount = tbMsg.getAndIncrementRuleNodeCounter();
int maxRuleNodeExecutionsPerMessage = getTenantProfileConfiguration().getMaxRuleNodeExecsPerMessage();
if (maxRuleNodeExecutionsPerMessage == 0 || ruleNodeCount < maxRuleNodeExecutionsPerMessage) {
apiUsageClient.report(tenantId, tbMsg.getCustomerId(), ApiUsageRecordKey.RE_EXEC_COUNT);
if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());
}
try {
tbNode.onMsg(msg.getCtx(), msg.getMsg());
} catch (Exception e) {
msg.getCtx().tellFailure(msg.getMsg(), e);
}
} else {
tbMsg.getCallback().onFailure(new RuleNodeException("Message is processed by more then " + maxRuleNodeExecutionsPerMessage + " rule nodes!", ruleChainName, ruleNode));
}
}
}
@ -160,4 +179,23 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
protected RuleNodeException getInactiveException() {
return new RuleNodeException("Rule Node is not active! Failed to initialize.", ruleChainName, ruleNode);
}
private boolean isMyNodePartition() {
return !ruleNode.isSingletonMode()
|| systemContext.getDiscoveryService().isMonolith()
|| defaultCtx.isLocalEntity(ruleNode.getId());
}
//Message will return after processing. See RuleChainActorMessageProcessor.pushToTarget.
private void putToNodePartition(TbMsg source) {
TbMsg tbMsg = TbMsg.newMsg(source, source.getQueueName(), source.getRuleChainId(), entityId);
TopicPartitionInfo tpi = systemContext.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, ruleNode.getId());
TransportProtos.ToRuleEngineMsg toQueueMsg = TransportProtos.ToRuleEngineMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setTbMsg(TbMsg.toByteString(tbMsg))
.build();
systemContext.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), toQueueMsg, null);
defaultCtx.ack(source);
}
}

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

33
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;
@ -90,11 +91,11 @@ public class EntityActionService {
case ALARM_CLEAR:
msgType = DataConstants.ALARM_CLEAR;
break;
case ALARM_ASSIGN:
msgType = DataConstants.ALARM_ASSIGN;
case ALARM_ASSIGNED:
msgType = DataConstants.ALARM_ASSIGNED;
break;
case ALARM_UNASSIGN:
msgType = DataConstants.ALARM_UNASSIGN;
case ALARM_UNASSIGNED:
msgType = DataConstants.ALARM_UNASSIGNED;
break;
case ALARM_DELETE:
msgType = DataConstants.ALARM_DELETE;
@ -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);

1
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -155,6 +155,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
scannedComponent.setName(ruleNodeAnnotation.name());
scannedComponent.setScope(ruleNodeAnnotation.scope());
scannedComponent.setClusteringMode(ruleNodeAnnotation.clusteringMode());
NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
ObjectNode configurationDescriptor = mapper.createObjectNode();
JsonNode node = mapper.valueToTree(nodeDefinition);

8
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java

@ -309,10 +309,10 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
return EdgeEventActionType.ALARM_ACK;
case ALARM_CLEAR:
return EdgeEventActionType.ALARM_CLEAR;
case ALARM_ASSIGN:
return EdgeEventActionType.ALARM_ASSIGN;
case ALARM_UNASSIGN:
return EdgeEventActionType.ALARM_UNASSIGN;
case ALARM_ASSIGNED:
return EdgeEventActionType.ALARM_ASSIGNED;
case ALARM_UNASSIGNED:
return EdgeEventActionType.ALARM_UNASSIGNED;
case DELETED:
return EdgeEventActionType.DELETED;
case RELATION_ADD_OR_UPDATE:

4
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -175,7 +175,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ASSIGN, user);
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_ASSIGNED, user);
} else {
throw new ThingsboardException("Alarm was already assigned to this user!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
@ -203,7 +203,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
} catch (ThingsboardException e) {
log.error("Failed to save alarm comment", e);
}
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGN, user);
notificationEntityService.notifyCreateOrUpdateAlarm(result.getAlarm(), ActionType.ALARM_UNASSIGNED, user);
} else {
throw new ThingsboardException("Alarm was already unassigned!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}

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");
}
}

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

@ -32,19 +32,20 @@ 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> {
@Override
public boolean matchesFilter(RuleEngineMsgTrigger trigger, AlarmAssignmentNotificationRuleTriggerConfig triggerConfig) {
Action action = trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGN) ? Action.ASSIGNED : Action.UNASSIGNED;
Action action = trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGNED) ? Action.ASSIGNED : Action.UNASSIGNED;
if (!triggerConfig.getNotifyOn().contains(action)) {
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));
}
@ -53,7 +54,7 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
AlarmInfo alarmInfo = JacksonUtil.fromString(trigger.getMsg().getData(), AlarmInfo.class);
AlarmAssignee assignee = alarmInfo.getAssignee();
return AlarmAssignmentNotificationInfo.builder()
.action(trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGN) ? "assigned" : "unassigned")
.action(trigger.getMsg().getType().equals(DataConstants.ALARM_ASSIGNED) ? "assigned" : "unassigned")
.assigneeFirstName(assignee != null ? assignee.getFirstName() : null)
.assigneeLastName(assignee != null ? assignee.getLastName() : null)
.assigneeEmail(assignee != null ? assignee.getEmail() : null)
@ -78,7 +79,7 @@ public class AlarmAssignmentTriggerProcessor implements RuleEngineMsgNotificatio
@Override
public Set<String> getSupportedMsgTypes() {
return Set.of(DataConstants.ALARM_ASSIGN, DataConstants.ALARM_UNASSIGN);
return Set.of(DataConstants.ALARM_ASSIGNED, DataConstants.ALARM_UNASSIGNED);
}
}

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()));

19
application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java

@ -29,7 +29,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@ -180,7 +179,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
alarm = updatedAlarm;
alarm.setAssigneeId(null);
@ -192,7 +191,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN);
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGNED);
}
@Test
@ -414,7 +413,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
}
@Test
@ -445,7 +444,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
logout();
@ -462,7 +461,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ASSIGN);
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ASSIGNED);
}
@Test
@ -480,7 +479,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
beforeAssignmentTs = System.currentTimeMillis();
Thread.sleep(2);
@ -491,7 +490,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN);
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGNED);
}
@Test
@ -509,7 +508,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGNED);
logout();
loginCustomerUser();
@ -525,7 +524,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGN);
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGNED);
}
@Test

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());
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -74,8 +74,8 @@ public class DataConstants {
public static final String TIMESERIES_DELETED = "TIMESERIES_DELETED";
public static final String ALARM_ACK = "ALARM_ACK";
public static final String ALARM_CLEAR = "ALARM_CLEAR";
public static final String ALARM_ASSIGN = "ALARM_ASSIGN";
public static final String ALARM_UNASSIGN = "ALARM_UNASSIGN";
public static final String ALARM_ASSIGNED = "ALARM_ASSIGNED";
public static final String ALARM_UNASSIGNED = "ALARM_UNASSIGNED";
public static final String ALARM_DELETE = "ALARM_DELETE";
public static final String COMMENT_CREATED = "COMMENT_CREATED";
public static final String COMMENT_UPDATED = "COMMENT_UPDATED";

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;

4
common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java

@ -40,8 +40,8 @@ public enum ActionType {
ALARM_ACK(false),
ALARM_CLEAR(false),
ALARM_DELETE(false),
ALARM_ASSIGN(false),
ALARM_UNASSIGN(false),
ALARM_ASSIGNED(false),
ALARM_UNASSIGNED(false),
LOGIN(false),
LOGOUT(false),
LOCKOUT(false),

4
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventActionType.java

@ -31,8 +31,8 @@ public enum EdgeEventActionType {
RPC_CALL,
ALARM_ACK,
ALARM_CLEAR,
ALARM_ASSIGN,
ALARM_UNASSIGN,
ALARM_ASSIGNED,
ALARM_UNASSIGNED,
ASSIGNED_TO_EDGE,
UNASSIGNED_FROM_EDGE,
CREDENTIALS_REQUEST,

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 ? "@" : "#";
}
}

29
common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentClusteringMode.java

@ -0,0 +1,29 @@
/**
* 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.plugin;
/**
* The main idea to use this - it's adding the ability to start rule nodes in singleton mode in cluster setup
* (singleton rule node will start in only one Rule Engine instance)
* USER_PREFERENCE - user has ability to configure clustering mode (enable/disable singleton mode in rule node config)
* ENABLE - user doesn't have ability to configure clustering mode (singleton mode is always FALSE in rule node config)
* SINGLETON - user doesn't have ability to configure clustering mode (singleton mode is always TRUE in rule node config)
*/
public enum ComponentClusteringMode {
USER_PREFERENCE,
ENABLED,
SINGLETON
}

10
common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java

@ -36,15 +36,17 @@ public class ComponentDescriptor extends SearchTextBased<ComponentDescriptorId>
@Getter @Setter private ComponentType type;
@ApiModelProperty(position = 4, value = "Scope of the Rule Node. Always set to 'TENANT', since no rule chains on the 'SYSTEM' level yet.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, allowableValues = "TENANT", example = "TENANT")
@Getter @Setter private ComponentScope scope;
@ApiModelProperty(position = 5, value = "Clustering mode of the RuleNode. This mode represents the ability to start Rule Node in multiple microservices.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, allowableValues = "USER_PREFERENCE, ENABLED, SINGLETON", example = "ENABLED")
@Getter @Setter private ComponentClusteringMode clusteringMode;
@Length(fieldName = "name")
@ApiModelProperty(position = 5, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "Custom Rule Node")
@ApiModelProperty(position = 6, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "Custom Rule Node")
@Getter @Setter private String name;
@ApiModelProperty(position = 6, value = "Full name of the Java class that implements the Rule Engine Node interface.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "com.mycompany.CustomRuleNode")
@ApiModelProperty(position = 7, value = "Full name of the Java class that implements the Rule Engine Node interface.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "com.mycompany.CustomRuleNode")
@Getter @Setter private String clazz;
@ApiModelProperty(position = 7, value = "Complex JSON object that represents the Rule Node configuration.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@ApiModelProperty(position = 8, value = "Complex JSON object that represents the Rule Node configuration.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Getter @Setter private transient JsonNode configurationDescriptor;
@Length(fieldName = "actions")
@ApiModelProperty(position = 8, value = "Rule Node Actions. Deprecated. Always null.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@ApiModelProperty(position = 9, value = "Rule Node Actions. Deprecated. Always null.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Getter @Setter private String actions;
public ComponentDescriptor() {

5
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java

@ -48,7 +48,9 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
private String name;
@ApiModelProperty(position = 6, value = "Enable/disable debug. ", example = "false")
private boolean debugMode;
@ApiModelProperty(position = 7, value = "JSON with the rule node configuration. Structure depends on the rule node implementation.", dataType = "com.fasterxml.jackson.databind.JsonNode")
@ApiModelProperty(position = 7, value = "Enable/disable singleton mode. ", example = "false")
private boolean singletonMode;
@ApiModelProperty(position = 8, value = "JSON with the rule node configuration. Structure depends on the rule node implementation.", dataType = "com.fasterxml.jackson.databind.JsonNode")
private transient JsonNode configuration;
@JsonIgnore
private byte[] configurationBytes;
@ -69,6 +71,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo<RuleNodeId> impl
this.type = ruleNode.getType();
this.name = ruleNode.getName();
this.debugMode = ruleNode.isDebugMode();
this.singletonMode = ruleNode.isSingletonMode();
this.setConfiguration(ruleNode.getConfiguration());
this.externalId = ruleNode.getExternalId();
}

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

74
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java

@ -18,7 +18,9 @@ package org.thingsboard.script.api.tbel;
import org.mvel2.ExecutionContext;
import org.mvel2.ParserConfiguration;
import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap;
import org.mvel2.util.MethodStub;
import org.thingsboard.server.common.data.StringUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@ -27,8 +29,12 @@ import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TbUtils {
@ -89,6 +95,14 @@ public class TbUtils {
byte[].class)));
parserConfig.addImport("bytesToHex", new MethodStub(TbUtils.class.getMethod("bytesToHex",
ExecutionArrayList.class)));
parserConfig.addImport("toFlatMap", new MethodStub(TbUtils.class.getMethod("toFlatMap",
ExecutionContext.class, Map.class)));
parserConfig.addImport("toFlatMap", new MethodStub(TbUtils.class.getMethod("toFlatMap",
ExecutionContext.class, Map.class, boolean.class)));
parserConfig.addImport("toFlatMap", new MethodStub(TbUtils.class.getMethod("toFlatMap",
ExecutionContext.class, Map.class, List.class)));
parserConfig.addImport("toFlatMap", new MethodStub(TbUtils.class.getMethod("toFlatMap",
ExecutionContext.class, Map.class, List.class, boolean.class)));
}
public static String btoa(String input) {
@ -225,7 +239,7 @@ public class TbUtils {
}
ExecutionArrayList<Byte> data = new ExecutionArrayList<>(ctx);
for (int i = 0; i < len; i += 2) {
data.add((byte)((Character.digit(hex.charAt(i), 16) << 4)
data.add((byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16)));
}
return data;
@ -316,4 +330,62 @@ public class TbUtils {
}
return value;
}
public static ExecutionHashMap<String, Object> toFlatMap(ExecutionContext ctx, Map<String, Object> json) {
return toFlatMap(ctx, json, new ArrayList<>(), true);
}
public static ExecutionHashMap<String, Object> toFlatMap(ExecutionContext ctx, Map<String, Object> json, boolean pathInKey) {
return toFlatMap(ctx, json, new ArrayList<>(), pathInKey);
}
public static ExecutionHashMap<String, Object> toFlatMap(ExecutionContext ctx, Map<String, Object> json, List<String> excludeList) {
return toFlatMap(ctx, json, excludeList, true);
}
public static ExecutionHashMap<String, Object> toFlatMap(ExecutionContext ctx, Map<String, Object> json, List<String> excludeList, boolean pathInKey) {
ExecutionHashMap<String, Object> map = new ExecutionHashMap<>(16, ctx);
parseRecursive(json, map, excludeList, "", pathInKey);
return map;
}
private static void parseRecursive(Object json, Map<String, Object> map, List<String> excludeList, String path, boolean pathInKey) {
if (json instanceof Map.Entry) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) json;
if (StringUtils.isNotBlank(path)) {
path += ".";
}
if (excludeList.contains(entry.getKey())) {
return;
}
path += entry.getKey();
json = entry.getValue();
}
if (json instanceof Set || json instanceof List) {
String arrayPath = path + ".";
Object[] collection = ((Collection<?>) json).toArray();
for (int index = 0; index < collection.length; index++) {
parseRecursive(collection[index], map, excludeList, arrayPath + index, pathInKey);
}
} else if (json instanceof Map) {
Map<?, ?> node = (Map<?, ?>) json;
for (Map.Entry<?, ?> entry : node.entrySet()) {
parseRecursive(entry, map, excludeList, path, pathInKey);
}
} else {
if (pathInKey) {
map.put(path, json);
} else {
String key = path.substring(path.lastIndexOf('.') + 1);
if (StringUtils.isNumeric(key)) {
int pos = path.length();
for (int i = 0; i < 2; i++) {
pos = path.lastIndexOf('.', pos - 1);
}
key = path.substring(pos + 1);
}
map.put(key, json);
}
}
}
}

100
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java

@ -15,15 +15,50 @@
*/
package org.thingsboard.script.api.tbel;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mvel2.ExecutionContext;
import org.mvel2.ParserContext;
import org.mvel2.SandboxedParserConfiguration;
import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Random;
@Slf4j
public class TbUtilsTest {
private ExecutionContext ctx;
@Before
public void before() {
SandboxedParserConfiguration parserConfig = ParserContext.enableSandboxedMode();
parserConfig.addImport("JSON", TbJson.class);
parserConfig.registerDataType("Date", TbDate.class, date -> 8L);
parserConfig.registerDataType("Random", Random.class, date -> 8L);
parserConfig.registerDataType("Calendar", Calendar.class, date -> 8L);
try {
TbUtils.register(parserConfig);
} catch (Exception e) {
log.error("Cannot register functions", e);
}
ctx = new ExecutionContext(parserConfig);
Assert.assertNotNull(ctx);
}
@After
public void after() {
ctx.stop();
}
@Test
public void parseHexToInt() {
Assert.assertEquals(0xAB, TbUtils.parseHexToInt("AB"));
@ -87,6 +122,71 @@ public class TbUtilsTest {
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, false));
}
@Test
public void toFlatMap() {
ExecutionHashMap<String, Object> inputMap = new ExecutionHashMap<>(16, ctx);
inputMap.put("name", "Alice");
inputMap.put("age", 30);
inputMap.put("devices", new ExecutionArrayList<>(List.of(
new ExecutionHashMap<>(16, ctx) {{
put("id", "dev001");
put("type", "sensor");
}},
new ExecutionHashMap<>(16, ctx) {{
put("id", "dev002");
put("type", "actuator");
}}
), ctx));
inputMap.put("settings", new ExecutionHashMap<>(16, ctx) {{
put("notifications", true);
put("timezone", "UTC-5");
put("params", new ExecutionHashMap<>(16, ctx) {{
put("param1", "value1");
put("param2", "value2");
put("param3", new ExecutionHashMap<>(16, ctx) {{
put("subParam1", "value1");
put("subParam2", "value2");
}});
}});
}});
ExecutionArrayList<String> excludeList = new ExecutionArrayList<>(ctx);
excludeList.addAll(List.of("age", "id", "param1", "subParam2"));
ExecutionHashMap<String, Object> expectedMapWithPath = new ExecutionHashMap<>(16, ctx);
expectedMapWithPath.put("name", "Alice");
expectedMapWithPath.put("devices.0.type", "sensor");
expectedMapWithPath.put("devices.1.type", "actuator");
expectedMapWithPath.put("settings.notifications", true);
expectedMapWithPath.put("settings.timezone", "UTC-5");
expectedMapWithPath.put("settings.params.param2", "value2");
expectedMapWithPath.put("settings.params.param3.subParam1", "value1");
ExecutionHashMap<String, Object> actualMapWithPaths = TbUtils.toFlatMap(ctx, inputMap, excludeList, true);
Assert.assertEquals(expectedMapWithPath, actualMapWithPaths);
ExecutionHashMap<String, Object> expectedMapWithoutPaths = new ExecutionHashMap<>(16, ctx);
expectedMapWithoutPaths.put("timezone", "UTC-5");
expectedMapWithoutPaths.put("name", "Alice");
expectedMapWithoutPaths.put("id", "dev002");
expectedMapWithoutPaths.put("subParam2", "value2");
expectedMapWithoutPaths.put("type", "actuator");
expectedMapWithoutPaths.put("subParam1", "value1");
expectedMapWithoutPaths.put("param1", "value1");
expectedMapWithoutPaths.put("notifications", true);
expectedMapWithoutPaths.put("age", 30);
expectedMapWithoutPaths.put("param2", "value2");
ExecutionHashMap<String, Object> actualMapWithoutPaths = TbUtils.toFlatMap(ctx, inputMap, false);
Assert.assertEquals(expectedMapWithoutPaths, actualMapWithoutPaths);
}
private static String keyToValue(String key, String extraSymbol) {
return key + "Value" + (extraSymbol == null ? "" : extraSymbol);
}
private static List<Byte> toList(byte[] data) {
List<Byte> result = new ArrayList<>(data.length);
for (Byte b : data) {

4
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -166,8 +166,8 @@ public class AuditLogServiceImpl implements AuditLogService {
case UPDATED:
case ALARM_ACK:
case ALARM_CLEAR:
case ALARM_ASSIGN:
case ALARM_UNASSIGN:
case ALARM_ASSIGNED:
case ALARM_UNASSIGNED:
case RELATIONS_DELETED:
case ASSIGNED_TO_TENANT:
if (entity != null) {

2
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -403,6 +403,7 @@ public class ModelConstants {
public static final String COMPONENT_DESCRIPTOR_COLUMN_FAMILY_NAME = "component_descriptor";
public static final String COMPONENT_DESCRIPTOR_TYPE_PROPERTY = "type";
public static final String COMPONENT_DESCRIPTOR_SCOPE_PROPERTY = "scope";
public static final String COMPONENT_DESCRIPTOR_CLUSTERING_MODE_PROPERTY = "clustering_mode";
public static final String COMPONENT_DESCRIPTOR_NAME_PROPERTY = "name";
public static final String COMPONENT_DESCRIPTOR_CLASS_PROPERTY = "clazz";
public static final String COMPONENT_DESCRIPTOR_CONFIGURATION_DESCRIPTOR_PROPERTY = "configuration_descriptor";
@ -446,6 +447,7 @@ public class ModelConstants {
public static final String EVENT_MESSAGE_COLUMN_NAME = "e_message";
public static final String DEBUG_MODE = "debug_mode";
public static final String SINGLETON_MODE = "singleton_mode";
/**
* Cassandra rule chain constants.

7
dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java

@ -23,6 +23,7 @@ import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.ComponentDescriptorId;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentScope;
import org.thingsboard.server.common.data.plugin.ComponentClusteringMode;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
@ -50,6 +51,10 @@ public class ComponentDescriptorEntity extends BaseSqlEntity<ComponentDescriptor
@Column(name = ModelConstants.COMPONENT_DESCRIPTOR_SCOPE_PROPERTY)
private ComponentScope scope;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.COMPONENT_DESCRIPTOR_CLUSTERING_MODE_PROPERTY)
private ComponentClusteringMode clusteringMode;
@Column(name = ModelConstants.COMPONENT_DESCRIPTOR_NAME_PROPERTY)
private String name;
@ -77,6 +82,7 @@ public class ComponentDescriptorEntity extends BaseSqlEntity<ComponentDescriptor
this.actions = component.getActions();
this.type = component.getType();
this.scope = component.getScope();
this.clusteringMode = component.getClusteringMode();
this.name = component.getName();
this.clazz = component.getClazz();
this.configurationDescriptor = component.getConfigurationDescriptor();
@ -89,6 +95,7 @@ public class ComponentDescriptorEntity extends BaseSqlEntity<ComponentDescriptor
data.setCreatedTime(createdTime);
data.setType(type);
data.setScope(scope);
data.setClusteringMode(clusteringMode);
data.setName(this.getName());
data.setClazz(this.getClazz());
data.setActions(this.getActions());

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java

@ -64,6 +64,9 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
@Column(name = ModelConstants.DEBUG_MODE)
private boolean debugMode;
@Column(name = ModelConstants.SINGLETON_MODE)
private boolean singletonMode;
@Column(name = ModelConstants.EXTERNAL_ID_PROPERTY)
private UUID externalId;
@ -81,6 +84,7 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
this.type = ruleNode.getType();
this.name = ruleNode.getName();
this.debugMode = ruleNode.isDebugMode();
this.singletonMode = ruleNode.isSingletonMode();
this.searchText = ruleNode.getName();
this.configuration = ruleNode.getConfiguration();
this.additionalInfo = ruleNode.getAdditionalInfo();
@ -109,6 +113,7 @@ public class RuleNodeEntity extends BaseSqlEntity<RuleNode> implements SearchTex
ruleNode.setType(type);
ruleNode.setName(name);
ruleNode.setDebugMode(debugMode);
ruleNode.setSingletonMode(singletonMode);
ruleNode.setConfiguration(configuration);
ruleNode.setAdditionalInfo(additionalInfo);
if (externalId != null) {

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")

29
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentClusteringMode;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
@ -50,6 +51,7 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.common.data.util.ReflectionUtils;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityCountService;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -154,6 +156,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
Map<RuleNodeId, Integer> ruleNodeIndexMap = new HashMap<>();
if (nodes != null) {
for (RuleNode node : nodes) {
setSingletonMode(node);
if (node.getId() != null) {
ruleNodeIndexMap.put(node.getId(), nodes.indexOf(node));
} else {
@ -783,4 +786,30 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
};
private void setSingletonMode(RuleNode ruleNode) {
boolean singletonMode;
try {
ComponentClusteringMode nodeConfigType = ReflectionUtils.getAnnotationProperty(ruleNode.getType(),
"org.thingsboard.rule.engine.api.RuleNode", "clusteringMode");
switch (nodeConfigType) {
case ENABLED:
singletonMode = false;
break;
case SINGLETON:
singletonMode = true;
break;
case USER_PREFERENCE:
default:
singletonMode = ruleNode.isSingletonMode();
break;
}
} catch (Exception e) {
log.warn("Failed to get clustering mode: {}", ExceptionUtils.getRootCauseMessage(e));
singletonMode = false;
}
ruleNode.setSingletonMode(singletonMode);
}
}

4
dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java

@ -30,14 +30,12 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.util.ReflectionUtils;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.rule.RuleChainDao;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.HashMap;
@ -62,7 +60,7 @@ public class RuleChainDataValidator extends DataValidator<RuleChain> {
@Override
protected void validateCreate(TenantId tenantId, RuleChain data) {
validateNumberOfEntitiesPerTenant(tenantId, EntityType.RULE_CHAIN);
validateNumberOfEntitiesPerTenant(tenantId, EntityType.RULE_CHAIN);
}
@Override

3
dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java

@ -76,7 +76,8 @@ public abstract class AbstractComponentDescriptorInsertRepository implements Com
.setParameter("name", entity.getName())
.setParameter("scope", entity.getScope().name())
.setParameter("search_text", entity.getSearchText())
.setParameter("type", entity.getType().name());
.setParameter("type", entity.getType().name())
.setParameter("clustering_mode", entity.getClusteringMode().name());
}
private ComponentDescriptorEntity processSaveOrUpdate(ComponentDescriptorEntity entity, String query) {

4
dao/src/main/java/org/thingsboard/server/dao/sql/component/SqlComponentDescriptorInsertRepository.java

@ -44,10 +44,10 @@ public class SqlComponentDescriptorInsertRepository extends AbstractComponentDes
}
private static String getInsertOrUpdateStatement(String conflictKeyStatement, String updateKeyStatement) {
return "INSERT INTO component_descriptor (id, created_time, actions, clazz, configuration_descriptor, name, scope, search_text, type) VALUES (:id, :created_time, :actions, :clazz, :configuration_descriptor, :name, :scope, :search_text, :type) ON CONFLICT " + conflictKeyStatement + " DO UPDATE SET " + updateKeyStatement + " returning *";
return "INSERT INTO component_descriptor (id, created_time, actions, clazz, configuration_descriptor, name, scope, search_text, type, clustering_mode) VALUES (:id, :created_time, :actions, :clazz, :configuration_descriptor, :name, :scope, :search_text, :type, :clustering_mode) ON CONFLICT " + conflictKeyStatement + " DO UPDATE SET " + updateKeyStatement + " returning *";
}
private static String getUpdateStatement(String id) {
return "actions = :actions, " + id + ",created_time = :created_time, configuration_descriptor = :configuration_descriptor, name = :name, scope = :scope, search_text = :search_text, type = :type";
return "actions = :actions, " + id + ",created_time = :created_time, configuration_descriptor = :configuration_descriptor, name = :name, scope = :scope, search_text = :search_text, type = :type, clustering_mode = :clustering_mode";
}
}

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);

4
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java

@ -312,9 +312,8 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
public long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query) {
QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, null, EntityType.ALARM));
ctx.append("select count(id) from alarm_info a ");
if (query.isSearchPropagatedAlarms()) {
ctx.append("select count(distinct(a.id)) from alarm_info a ");
ctx.append(JOIN_ENTITY_ALARMS);
ctx.append("where a.tenant_id = :tenantId and ea.tenant_id = :tenantId");
ctx.addUuidParameter("tenantId", tenantId.getId());
@ -323,6 +322,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
ctx.addUuidParameter("customerId", customerId.getId());
}
} else {
ctx.append("select count(id) from alarm_info a ");
ctx.append("where a.tenant_id = :tenantId");
ctx.addUuidParameter("tenantId", tenantId.getId());
if (customerId != null && !customerId.isNullUid()) {

4
dao/src/main/resources/sql/schema-entities.sql

@ -125,7 +125,8 @@ CREATE TABLE IF NOT EXISTS component_descriptor (
name varchar(255),
scope varchar(255),
search_text varchar(255),
type varchar(255)
type varchar(255),
clustering_mode varchar(255)
);
CREATE TABLE IF NOT EXISTS customer (
@ -187,6 +188,7 @@ CREATE TABLE IF NOT EXISTS rule_node (
type varchar(255),
name varchar(255),
debug_mode boolean,
singleton_mode boolean,
search_text varchar(255),
external_id uuid
);

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractBasePage.java

@ -117,7 +117,7 @@ abstract public class AbstractBasePage {
try {
return wait.until(ExpectedConditions.not(ExpectedConditions.visibilityOfElementLocated(By.xpath(locator))));
} catch (WebDriverException e) {
return fail("Element is present");
return fail("Element is present: " + locator);
}
}
@ -125,7 +125,7 @@ abstract public class AbstractBasePage {
try {
return wait.until(ExpectedConditions.not(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.xpath(locator))));
} catch (WebDriverException e) {
return fail("Elements is present");
return fail("Elements is present: " + locator);
}
}

67
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/base/AbstractDriverBaseTest.java

@ -47,8 +47,11 @@ import java.io.ByteArrayInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.thingsboard.server.msa.TestProperties.getBaseUiUrl;
import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL;
@ -62,9 +65,11 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
private static final int WIDTH = 1680;
private static final int HEIGHT = 1050;
private static final String REMOTE_WEBDRIVER_HOST = "http://localhost:4444";
protected static final PageLink pageLink = new PageLink(10);
private static final ContainerTestSuite instance = ContainerTestSuite.getInstance();
protected final PageLink pageLink = new PageLink(10);
private final ContainerTestSuite instance = ContainerTestSuite.getInstance();
private JavascriptExecutor js;
public static final long WAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
private final Duration duration = Duration.ofMillis(WAIT_TIMEOUT);
@BeforeClass
public void startUp() throws MalformedURLException {
@ -102,8 +107,7 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
}
public String getJwtTokenFromLocalStorage() {
js = (JavascriptExecutor) driver;
return (String) js.executeScript("return window.localStorage.getItem('jwt_token');");
return (String) getJs().executeScript("return window.localStorage.getItem('jwt_token');");
}
public void openBaseUiUrl() {
@ -119,7 +123,7 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
}
protected boolean urlContains(String urlPath) {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofMillis(5000));
WebDriverWait wait = new WebDriverWait(driver, duration);
try {
wait.until(ExpectedConditions.urlContains(urlPath));
} catch (WebDriverException e) {
@ -129,21 +133,22 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
}
public void jsClick(WebElement element) {
js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].click();", element);
getJs().executeScript("arguments[0].click();", element);
}
public static RuleChain getRuleChainByName(String name) {
try {
return testRestClient.getRuleChains(pageLink).getData().stream()
.filter(s -> s.getName().equals(name)).collect(Collectors.toList()).get(0);
} catch (Exception e) {
log.error("No such rule chain with name: " + name);
return null;
}
public RuleChain getRuleChainByName(String name) {
return testRestClient.getRuleChains(pageLink).getData().stream()
.filter(s -> s.getName().equals(name))
.findFirst().orElse(null);
}
public static Customer getCustomerByName(String name) {
public List<RuleChain> getRuleChainsByName(String name) {
return testRestClient.getRuleChains(pageLink).getData().stream()
.filter(s -> s.getName().equals(name))
.collect(Collectors.toList());
}
public Customer getCustomerByName(String name) {
try {
return testRestClient.getCustomers(pageLink).getData().stream()
.filter(x -> x.getName().equals(name)).collect(Collectors.toList()).get(0);
@ -153,7 +158,7 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
}
}
public static DeviceProfile getDeviceProfileByName(String name) {
public DeviceProfile getDeviceProfileByName(String name) {
try {
return testRestClient.getDeviceProfiles(pageLink).getData().stream()
.filter(x -> x.getName().equals(name)).collect(Collectors.toList()).get(0);
@ -163,7 +168,7 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
}
}
public static AssetProfile getAssetProfileByName(String name) {
public AssetProfile getAssetProfileByName(String name) {
try {
return testRestClient.getAssetProfiles(pageLink).getData().stream()
.filter(x -> x.getName().equals(name)).collect(Collectors.toList()).get(0);
@ -179,4 +184,30 @@ abstract public class AbstractDriverBaseTest extends AbstractContainerTest {
new ByteArrayInputStream(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES)));
}
}
public JavascriptExecutor getJs() {
return js = (JavascriptExecutor) driver;
}
public void assertIsDisplayed(WebElement element) {
assertThat(element.isDisplayed()).as(element + " is displayed").isTrue();
}
public void assertIsDisable(WebElement element) {
assertThat(element.isEnabled()).as(element + " is disabled").isFalse();
}
public void deleteRuleChainByName(String ruleChainName) {
List<RuleChain> ruleChains = getRuleChainsByName(ruleChainName);
if (!ruleChains.isEmpty()) {
ruleChains.forEach(rc -> testRestClient.deleteRuleChain(rc.getId()));
}
}
public void setRootRuleChain(String ruleChainName) {
List<RuleChain> ruleChains = getRuleChainsByName(ruleChainName);
if (!ruleChains.isEmpty()) {
testRestClient.setRootRuleChain(ruleChains.stream().findFirst().get().getId());
}
}
}

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/OtherPageElements.java

@ -33,7 +33,7 @@ public class OtherPageElements extends AbstractBasePage {
private static final String ENTITY_COUNT = "//div[@class='mat-paginator-range-label']";
private static final String WARNING_DELETE_POPUP_YES = "//tb-confirm-dialog//button[2]";
private static final String WARNING_DELETE_POPUP_TITLE = "//tb-confirm-dialog/h2";
private static final String REFRESH_BTN = "//mat-icon[contains(text(),'refresh')]/..";
private static final String REFRESH_BTN = "//mat-icon[contains(text(),'refresh')]/parent::button";
private static final String HELP_BTN = "//mat-icon[contains(text(),'help')]/ancestor::button";
private static final String CHECKBOX = "//mat-row//span[contains(text(),'%s')]/../..//mat-checkbox";
private static final String CHECKBOXES = "//tbody//mat-checkbox";

4
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/OtherPageElementsHelper.java

@ -15,8 +15,6 @@
*/
package org.thingsboard.server.msa.ui.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
public class OtherPageElementsHelper extends OtherPageElements {
@ -34,7 +32,7 @@ public class OtherPageElementsHelper extends OtherPageElements {
return headerName;
}
public boolean entityIsNotPresent(String entityName) {
public boolean assertEntityIsNotPresent(String entityName) {
return elementIsNotPresent(getEntity(entityName));
}

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/pages/RuleChainsPageHelper.java

@ -107,7 +107,7 @@ public class RuleChainsPageHelper extends RuleChainsPageElements {
return wait.until(ExpectedConditions.invisibilityOfElementLocated(By.xpath(getDeleteRuleChainFromViewBtn())));
}
public boolean ruleChainsIsNotPresent(String ruleChainName) {
public boolean assertRuleChainsIsNotPresent(String ruleChainName) {
return elementsIsNotPresent(getEntity(ruleChainName));
}

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/CreateAssetProfileImportTest.java

@ -98,7 +98,7 @@ public class CreateAssetProfileImportTest extends AbstractDriverBaseTest {
Assert.assertNotNull(profilesPage.importingFile(EMPTY_IMPORT_MESSAGE));
Assert.assertTrue(profilesPage.importingFile(EMPTY_IMPORT_MESSAGE).isDisplayed());
Assert.assertTrue(profilesPage.entityIsNotPresent(IMPORT_ASSET_PROFILE_NAME));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(IMPORT_ASSET_PROFILE_NAME));
}
@Epic("Asset profiles smoke")

8
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/assetProfileSmoke/DeleteAssetProfileTest.java

@ -55,7 +55,7 @@ public class DeleteAssetProfileTest extends AbstractDriverBaseTest {
profilesPage.warningPopUpYesBtn().click();
profilesPage.refreshBtn();
Assert.assertTrue(profilesPage.entityIsNotPresent(name));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(name));
}
@Epic("Asset profiles smoke")
@ -72,7 +72,7 @@ public class DeleteAssetProfileTest extends AbstractDriverBaseTest {
profilesPage.warningPopUpYesBtn().click();
profilesPage.refreshBtn();
Assert.assertTrue(profilesPage.entityIsNotPresent(name));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(name));
}
@Epic("Asset profiles smoke")
@ -89,7 +89,7 @@ public class DeleteAssetProfileTest extends AbstractDriverBaseTest {
profilesPage.warningPopUpYesBtn().click();
profilesPage.refreshBtn();
Assert.assertTrue(profilesPage.entityIsNotPresent(name));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(name));
}
@Epic("Asset profiles smoke")
@ -136,6 +136,6 @@ public class DeleteAssetProfileTest extends AbstractDriverBaseTest {
profilesPage.deleteBtn(name).click();
profilesPage.warningPopUpYesBtn().click();
Assert.assertTrue(profilesPage.entityIsNotPresent(name));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(name));
}
}

8
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/customerSmoke/DeleteCustomerTest.java

@ -57,7 +57,7 @@ public class DeleteCustomerTest extends AbstractDriverBaseTest {
String deletedCustomer = customerPage.deleteRuleChainTrash(customer);
customerPage.refreshBtn().click();
Assert.assertTrue(customerPage.entityIsNotPresent(deletedCustomer));
Assert.assertTrue(customerPage.assertEntityIsNotPresent(deletedCustomer));
}
@Epic("Customers smoke tests")
@ -72,7 +72,7 @@ public class DeleteCustomerTest extends AbstractDriverBaseTest {
String deletedCustomer = customerPage.deleteSelected(customerName);
ruleChainsPage.refreshBtn().click();
Assert.assertTrue(ruleChainsPage.entityIsNotPresent(deletedCustomer));
Assert.assertTrue(ruleChainsPage.assertEntityIsNotPresent(deletedCustomer));
}
@Epic("Customers smoke tests")
@ -89,7 +89,7 @@ public class DeleteCustomerTest extends AbstractDriverBaseTest {
customerPage.warningPopUpYesBtn().click();
jsClick(customerPage.refreshBtn());
Assert.assertTrue(customerPage.entityIsNotPresent(customerName));
Assert.assertTrue(customerPage.assertEntityIsNotPresent(customerName));
}
@Epic("Customers smoke tests")
@ -104,6 +104,6 @@ public class DeleteCustomerTest extends AbstractDriverBaseTest {
String deletedCustomer = customerPage.deleteRuleChainTrash(customer);
customerPage.refreshBtn().click();
Assert.assertTrue(customerPage.entityIsNotPresent(deletedCustomer));
Assert.assertTrue(customerPage.assertEntityIsNotPresent(deletedCustomer));
}
}

2
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/CreateDeviceProfileImportTest.java

@ -98,7 +98,7 @@ public class CreateDeviceProfileImportTest extends AbstractDriverBaseTest {
Assert.assertNotNull(profilesPage.importingFile(EMPTY_IMPORT_MESSAGE));
Assert.assertTrue(profilesPage.importingFile(EMPTY_IMPORT_MESSAGE).isDisplayed());
Assert.assertTrue(profilesPage.entityIsNotPresent(IMPORT_DEVICE_PROFILE_NAME));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(IMPORT_DEVICE_PROFILE_NAME));
}
@Epic("Device profile smoke tests")

8
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/deviceProfileSmoke/DeleteDeviceProfileTest.java

@ -55,7 +55,7 @@ public class DeleteDeviceProfileTest extends AbstractDriverBaseTest {
profilesPage.warningPopUpYesBtn().click();
profilesPage.refreshBtn();
Assert.assertTrue(profilesPage.entityIsNotPresent(name));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(name));
}
@Epic("Device profile smoke tests")
@ -72,7 +72,7 @@ public class DeleteDeviceProfileTest extends AbstractDriverBaseTest {
profilesPage.warningPopUpYesBtn().click();
profilesPage.refreshBtn();
Assert.assertTrue(profilesPage.entityIsNotPresent(name));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(name));
}
@Epic("Device profile smoke tests")
@ -89,7 +89,7 @@ public class DeleteDeviceProfileTest extends AbstractDriverBaseTest {
profilesPage.warningPopUpYesBtn().click();
profilesPage.refreshBtn();
Assert.assertTrue(profilesPage.entityIsNotPresent(name));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(name));
}
@Epic("Device profile smoke tests")
@ -135,6 +135,6 @@ public class DeleteDeviceProfileTest extends AbstractDriverBaseTest {
profilesPage.deleteBtn(name).click();
profilesPage.warningPopUpYesBtn().click();
Assert.assertTrue(profilesPage.entityIsNotPresent(name));
Assert.assertTrue(profilesPage.assertEntityIsNotPresent(name));
}
}

96
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/OpenRuleChainTest.java

@ -1,96 +0,0 @@
/**
* 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.msa.ui.tests.ruleChainsSmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.OpenRuleChainPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
public class OpenRuleChainTest extends AbstractDriverBaseTest {
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
private OpenRuleChainPageHelper openRuleChainPage;
private String ruleChainName;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
openRuleChainPage = new OpenRuleChainPageHelper(driver);
}
@AfterMethod
public void delete() {
if (ruleChainName != null) {
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainName).getId());
ruleChainName = null;
}
}
@Epic("Rule chains smoke tests")
@Feature("Open rule chain")
@Test(priority = 10, groups = "smoke")
@Description("Open the rule chain by clicking on its name")
public void openRuleChainByRightCornerBtn() {
String ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(EntityPrototypes.defaultRuleChainPrototype(ruleChainName));
this.ruleChainName = ruleChainName;
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.entity(ruleChainName).click();
openRuleChainPage.setHeadName();
Assert.assertTrue(urlContains(String.valueOf(getRuleChainByName(ruleChainName).getId())));
Assert.assertTrue(openRuleChainPage.headRuleChainName().isDisplayed());
Assert.assertTrue(openRuleChainPage.inputNode().isDisplayed());
Assert.assertEquals(ruleChainName, openRuleChainPage.getHeadName());
}
@Epic("Rule chains smoke tests")
@Feature("Open rule chain")
@Test(priority = 10, groups = "smoke")
@Description("Open the rule chain by clicking on the 'Open rule chain' button in the entity view")
public void openRuleChainByViewBtn() {
String ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(EntityPrototypes.defaultRuleChainPrototype(ruleChainName));
this.ruleChainName = ruleChainName;
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.detailsBtn(ruleChainName).click();
ruleChainsPage.openRuleChainFromViewBtn().click();
openRuleChainPage.setHeadName();
Assert.assertTrue(urlContains(String.valueOf(getRuleChainByName(ruleChainName).getId())));
Assert.assertTrue(openRuleChainPage.headRuleChainName().isDisplayed());
Assert.assertTrue(openRuleChainPage.inputNode().isDisplayed());
Assert.assertEquals(ruleChainName, openRuleChainPage.getHeadName());
}
}

49
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/AbstractRuleChainTest.java

@ -0,0 +1,49 @@
/**
* 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.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Epic;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.OpenRuleChainPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
@Epic("Rule chains smoke tests")
abstract public class AbstractRuleChainTest extends AbstractDriverBaseTest {
protected SideBarMenuViewElements sideBarMenuView;
protected RuleChainsPageHelper ruleChainsPage;
protected OpenRuleChainPageHelper openRuleChainPage;
protected String ruleChainName;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
openRuleChainPage = new OpenRuleChainPageHelper(driver);
}
@AfterMethod
public void delete() {
deleteRuleChainByName(ruleChainName);
ruleChainName = null;
}
}

78
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/CreateRuleChainImportTest.java → msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/CreateRuleChainImportTest.java

@ -13,56 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.ruleChainsSmoke;
package org.thingsboard.server.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.openqa.selenium.WebElement;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.OpenRuleChainPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
import java.util.ArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.utils.Const.EMPTY_IMPORT_MESSAGE;
import static org.thingsboard.server.msa.ui.utils.Const.IMPORT_RULE_CHAIN_FILE_NAME;
import static org.thingsboard.server.msa.ui.utils.Const.IMPORT_RULE_CHAIN_NAME;
import static org.thingsboard.server.msa.ui.utils.Const.IMPORT_TXT_FILE_NAME;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultRuleChainPrototype;
public class CreateRuleChainImportTest extends AbstractDriverBaseTest {
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
private OpenRuleChainPageHelper openRuleChainPage;
@Feature("Import rule chain")
public class CreateRuleChainImportTest extends AbstractRuleChainTest {
private final String absolutePathToFileImportRuleChain = getClass().getClassLoader().getResource(IMPORT_RULE_CHAIN_FILE_NAME).getPath();
private final String absolutePathToFileImportTxt = getClass().getClassLoader().getResource(IMPORT_TXT_FILE_NAME).getPath();
private String ruleChainName;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
openRuleChainPage = new OpenRuleChainPageHelper(driver);
}
@AfterMethod
public void delete() {
if (ruleChainName != null) {
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainName).getId());
ruleChainName = null;
}
}
@Epic("Rule chains smoke tests")
@Feature("Import rule chain")
@Test(priority = 10, groups = "smoke")
@Description("Drop json file")
public void importRuleChain() {
@ -70,12 +40,9 @@ public class CreateRuleChainImportTest extends AbstractDriverBaseTest {
ruleChainsPage.openImportRuleChainView();
ruleChainsPage.browseFile().sendKeys(absolutePathToFileImportRuleChain);
Assert.assertNotNull(ruleChainsPage.importingFile(IMPORT_RULE_CHAIN_FILE_NAME));
Assert.assertTrue(ruleChainsPage.importingFile(IMPORT_RULE_CHAIN_FILE_NAME).isDisplayed());
assertIsDisplayed(ruleChainsPage.importingFile(IMPORT_RULE_CHAIN_FILE_NAME));
}
@Epic("Rule chains smoke tests")
@Feature("Import rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Drop json file and delete it")
public void importRuleChainAndDeleteFile() {
@ -84,13 +51,10 @@ public class CreateRuleChainImportTest extends AbstractDriverBaseTest {
ruleChainsPage.browseFile().sendKeys(absolutePathToFileImportRuleChain);
ruleChainsPage.clearImportFileBtn().click();
Assert.assertNotNull(ruleChainsPage.importingFile(EMPTY_IMPORT_MESSAGE));
Assert.assertTrue(ruleChainsPage.importingFile(EMPTY_IMPORT_MESSAGE).isDisplayed());
Assert.assertTrue(ruleChainsPage.entityIsNotPresent(IMPORT_RULE_CHAIN_FILE_NAME));
assertIsDisplayed(ruleChainsPage.importingFile(EMPTY_IMPORT_MESSAGE));
ruleChainsPage.assertEntityIsNotPresent(IMPORT_RULE_CHAIN_FILE_NAME);
}
@Epic("Rule chains smoke tests")
@Feature("Import rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Import txt file")
public void importTxtFile() {
@ -98,12 +62,9 @@ public class CreateRuleChainImportTest extends AbstractDriverBaseTest {
ruleChainsPage.openImportRuleChainView();
ruleChainsPage.browseFile().sendKeys(absolutePathToFileImportTxt);
Assert.assertNotNull(ruleChainsPage.importingFile(EMPTY_IMPORT_MESSAGE));
Assert.assertTrue(ruleChainsPage.importingFile(EMPTY_IMPORT_MESSAGE).isDisplayed());
assertIsDisplayed(ruleChainsPage.importingFile(EMPTY_IMPORT_MESSAGE));
}
@Epic("Rule chains smoke tests")
@Feature("Import rule chain")
@Test(priority = 30, groups = "smoke")
@Description("Import rule chain")
public void importRuleChainAndSave() {
@ -116,18 +77,14 @@ public class CreateRuleChainImportTest extends AbstractDriverBaseTest {
ruleChainName = IMPORT_RULE_CHAIN_NAME;
sideBarMenuView.ruleChainsBtn().click();
Assert.assertNotNull(ruleChainsPage.entity(IMPORT_RULE_CHAIN_NAME));
Assert.assertTrue(ruleChainsPage.entity(IMPORT_RULE_CHAIN_NAME).isDisplayed());
assertIsDisplayed(ruleChainsPage.entity(ruleChainName));
}
@Epic("Rule chains smoke tests")
@Feature("Import rule chain")
@Test(priority = 40, groups = "smoke")
@Description("Import rule chain with same name")
public void importRuleChainAndSaveWithSameName() {
String ruleChainName = IMPORT_RULE_CHAIN_NAME;
ruleChainName = IMPORT_RULE_CHAIN_NAME;
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
this.ruleChainName = ruleChainName;
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.openImportRuleChainView();
@ -137,15 +94,8 @@ public class CreateRuleChainImportTest extends AbstractDriverBaseTest {
doneBtn.click();
sideBarMenuView.ruleChainsBtn().click();
boolean entityNotNull = ruleChainsPage.entity(ruleChainName) != null;
boolean entitiesSizeMoreOne = ruleChainsPage.entities(ruleChainName).size() > 1;
ArrayList<Boolean> entityIsDisplayed = new ArrayList<>();
ruleChainsPage.entities(ruleChainName).forEach(x -> entityIsDisplayed.add(x.isDisplayed()));
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainName).getId());
Assert.assertTrue(entityNotNull);
Assert.assertTrue(entitiesSizeMoreOne);
entityIsDisplayed.forEach(Assert::assertTrue);
assertThat(ruleChainsPage.entities(ruleChainName).size() > 1).
as("More than 1 rule chains have been created").isTrue();
ruleChainsPage.entities(ruleChainName).forEach(this::assertIsDisplayed);
}
}

97
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/CreateRuleChainTest.java → msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/CreateRuleChainTest.java

@ -13,54 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.ruleChainsSmoke;
package org.thingsboard.server.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import java.util.ArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random;
import static org.thingsboard.server.msa.ui.utils.Const.EMPTY_RULE_CHAIN_MESSAGE;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
public class CreateRuleChainTest extends AbstractDriverBaseTest {
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
private String ruleChainName;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
}
@AfterMethod
public void delete() {
if (ruleChainName != null) {
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainName).getId());
ruleChainName = null;
}
}
@Feature("Create rule chain")
public class CreateRuleChainTest extends AbstractRuleChainTest {
@Epic("Rule chains smoke tests")
@Feature("Create rule chain")
@Test(priority = 10, groups = "smoke")
@Description("Add rule chain after specifying the name (text/numbers /special characters)")
public void createRuleChain() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.openCreateRuleChainView();
@ -68,18 +39,14 @@ public class CreateRuleChainTest extends AbstractDriverBaseTest {
ruleChainsPage.nameField().sendKeys(ruleChainName);
ruleChainsPage.addBtnC().click();
ruleChainsPage.refreshBtn().click();
this.ruleChainName = ruleChainName;
Assert.assertNotNull(ruleChainsPage.entity(ruleChainName));
Assert.assertTrue(ruleChainsPage.entity(ruleChainName).isDisplayed());
assertIsDisplayed(ruleChainsPage.entity(ruleChainName));
}
@Epic("Rule chains smoke tests")
@Feature("Create rule chain")
@Test(priority = 10, groups = "smoke")
@Description("Add rule chain after specifying the name and description (text/numbers /special characters)")
public void createRuleChainWithDescription() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.openCreateRuleChainView();
@ -87,27 +54,23 @@ public class CreateRuleChainTest extends AbstractDriverBaseTest {
ruleChainsPage.descriptionAddEntityView().sendKeys(ruleChainName);
ruleChainsPage.addBtnC().click();
ruleChainsPage.refreshBtn().click();
this.ruleChainName = ruleChainName;
ruleChainsPage.detailsBtn(ruleChainName).click();
ruleChainsPage.setHeaderName();
Assert.assertEquals(ruleChainsPage.getHeaderName(), ruleChainName);
Assert.assertEquals(ruleChainsPage.descriptionEntityView().getAttribute("value"), ruleChainName);
assertThat(ruleChainsPage.getHeaderName()).as("Header of rule chain details tab").isEqualTo(ruleChainName);
assertThat(ruleChainsPage.descriptionEntityView().getAttribute("value"))
.as("Description in rule chain details tab").isEqualTo(ruleChainName);
}
@Epic("Rule chains smoke tests")
@Feature("Create rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Add rule chain without the name")
public void createRuleChainWithoutName() {
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.openCreateRuleChainView();
Assert.assertFalse(ruleChainsPage.addBtnV().isEnabled());
assertIsDisable(ruleChainsPage.addBtnV());
}
@Epic("Rule chains smoke tests")
@Feature("Create rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Create rule chain only with spase in name")
public void createRuleChainWithOnlySpace() {
@ -116,21 +79,16 @@ public class CreateRuleChainTest extends AbstractDriverBaseTest {
ruleChainsPage.nameField().sendKeys(" ");
ruleChainsPage.addBtnC().click();
Assert.assertNotNull(ruleChainsPage.warningMessage());
Assert.assertTrue(ruleChainsPage.warningMessage().isDisplayed());
Assert.assertEquals(ruleChainsPage.warningMessage().getText(), EMPTY_RULE_CHAIN_MESSAGE);
Assert.assertNotNull(ruleChainsPage.addEntityView());
Assert.assertTrue(ruleChainsPage.addEntityView().isDisplayed());
assertIsDisplayed(ruleChainsPage.warningMessage());
assertThat(ruleChainsPage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_RULE_CHAIN_MESSAGE);
assertIsDisplayed(ruleChainsPage.addEntityView());
}
@Epic("Rule chains smoke tests")
@Feature("Create rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Create a rule chain with the same name")
public void createRuleChainWithSameName() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(EntityPrototypes.defaultRuleChainPrototype(ruleChainName));
this.ruleChainName = ruleChainName;
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.openCreateRuleChainView();
@ -138,37 +96,24 @@ public class CreateRuleChainTest extends AbstractDriverBaseTest {
ruleChainsPage.addBtnC().click();
ruleChainsPage.refreshBtn().click();
boolean entityNotNull = ruleChainsPage.entity(ruleChainName) != null;
boolean entitiesSizeMoreOne = ruleChainsPage.entities(ruleChainName).size() > 1;
ArrayList<Boolean> entityIsDisplayed = new ArrayList<>();
ruleChainsPage.entities(ruleChainName).forEach(x -> entityIsDisplayed.add(x.isDisplayed()));
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainName).getId());
Assert.assertTrue(entityNotNull);
Assert.assertTrue(entitiesSizeMoreOne);
entityIsDisplayed.forEach(Assert::assertTrue);
assertThat(ruleChainsPage.entities(ruleChainName).size() > 1).
as("More than 1 rule chains have been created").isTrue();
ruleChainsPage.entities(ruleChainName).forEach(this::assertIsDisplayed);
}
@Epic("Rule chains smoke tests")
@Feature("Create rule chain")
@Test(priority = 30, groups = "smoke")
@Description("Add rule chain after specifying the name (text/numbers /special characters) without refresh")
public void createRuleChainWithoutRefresh() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.openCreateRuleChainView();
ruleChainsPage.nameField().sendKeys(ruleChainName);
ruleChainsPage.addBtnC().click();
this.ruleChainName = ruleChainName;
Assert.assertNotNull(ruleChainsPage.entity(ruleChainName));
Assert.assertTrue(ruleChainsPage.entity(ruleChainName).isDisplayed());
assertIsDisplayed(ruleChainsPage.entity(ruleChainName));
}
@Epic("Rule chains smoke tests")
@Feature("Create rule chain")
@Test(priority = 40, groups = "smoke")
@Description("Go to rule chain documentation page")
public void documentation() {
@ -179,6 +124,6 @@ public class CreateRuleChainTest extends AbstractDriverBaseTest {
ruleChainsPage.detailsBtn(ruleChainsPage.getRuleChainName()).click();
ruleChainsPage.goToHelpPage();
Assert.assertTrue(urlContains(urlPath), "URL contains " + urlPath);
assertThat(urlContains(urlPath)).as("Redirected URL contains " + urlPath).isTrue();
}
}

80
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/DeleteRuleChainTest.java → msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/DeleteRuleChainTest.java

@ -13,72 +13,53 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.ruleChainsSmoke;
package org.thingsboard.server.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random;
import static org.thingsboard.server.msa.ui.utils.Const.DELETE_RULE_CHAIN_WITH_PROFILE_MESSAGE;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
import static org.thingsboard.server.msa.ui.utils.Const.ROOT_RULE_CHAIN_NAME;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultRuleChainPrototype;
public class DeleteRuleChainTest extends AbstractDriverBaseTest {
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
@Feature("Delete rule chain")
public class DeleteRuleChainTest extends AbstractRuleChainTest {
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
}
@Epic("Rule chains smoke tests")
@Feature("Delete rule chain")
@Test(priority = 10, groups = "smoke")
@Description("Remove the rule chain by clicking on the trash icon in the right side of rule chain")
public void removeRuleChainByRightSideBtn() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
sideBarMenuView.ruleChainsBtn().click();
String deletedRuleChain = ruleChainsPage.deleteRuleChainTrash(ruleChainName);
ruleChainsPage.refreshBtn().click();
Assert.assertTrue(ruleChainsPage.entityIsNotPresent(deletedRuleChain));
ruleChainsPage.assertEntityIsNotPresent(deletedRuleChain);
}
@Epic("Rule chains smoke tests")
@Feature("Delete rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Remove rule chain by mark in the checkbox and then click on the trash can icon in the menu that appears at the top")
public void removeSelectedRuleChain() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
sideBarMenuView.ruleChainsBtn().click();
String deletedRuleChain = ruleChainsPage.deleteSelected(ruleChainName);
ruleChainsPage.refreshBtn().click();
Assert.assertTrue(ruleChainsPage.entityIsNotPresent(deletedRuleChain));
ruleChainsPage.assertEntityIsNotPresent(deletedRuleChain);
}
@Epic("Rule chains smoke tests")
@Feature("Delete rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Remove the rule chain by clicking on the 'Delete rule chain' btn in the entity view")
public void removeFromRuleChainView() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
sideBarMenuView.ruleChainsBtn().click();
@ -86,21 +67,17 @@ public class DeleteRuleChainTest extends AbstractDriverBaseTest {
String deletedRuleChain = ruleChainsPage.deleteRuleChainFromView(ruleChainName);
jsClick(ruleChainsPage.refreshBtn());
Assert.assertTrue(ruleChainsPage.entityIsNotPresent(deletedRuleChain));
ruleChainsPage.assertEntityIsNotPresent(deletedRuleChain);
}
@Epic("Rule chains smoke tests")
@Feature("Delete rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Remove the root rule chain by clicking on the trash icon in the right side of rule chain")
public void removeRootRuleChain() {
sideBarMenuView.ruleChainsBtn().click();
Assert.assertFalse(ruleChainsPage.deleteBtn(ROOT_RULE_CHAIN_NAME).isEnabled());
assertIsDisable(ruleChainsPage.deleteBtn(ROOT_RULE_CHAIN_NAME));
}
@Epic("Rule chains smoke tests")
@Feature("Delete rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Remove root rule chain by mark in the checkbox and then click on the trash can icon in the menu that appears at the top")
public void removeSelectedRootRuleChain() {
@ -117,7 +94,8 @@ public class DeleteRuleChainTest extends AbstractDriverBaseTest {
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.detailsBtn(ROOT_RULE_CHAIN_NAME).click();
Assert.assertTrue(ruleChainsPage.deleteBtnInRootRuleChainIsNotDisplayed());
assertThat(ruleChainsPage.deleteBtnInRootRuleChainIsNotDisplayed())
.as("Delete btn isn't displayed in details tab").isTrue();
}
@Epic("Rule chains smoke tests")
@ -132,15 +110,12 @@ public class DeleteRuleChainTest extends AbstractDriverBaseTest {
ruleChainsPage.warningPopUpYesBtn().click();
ruleChainsPage.refreshBtn().click();
Assert.assertNotNull(ruleChainsPage.entity(deletedRuleChain));
Assert.assertTrue(ruleChainsPage.entity(deletedRuleChain).isDisplayed());
Assert.assertNotNull(ruleChainsPage.warningMessage());
Assert.assertTrue(ruleChainsPage.warningMessage().isDisplayed());
Assert.assertEquals(ruleChainsPage.warningMessage().getText(), DELETE_RULE_CHAIN_WITH_PROFILE_MESSAGE);
assertIsDisplayed(ruleChainsPage.entity(deletedRuleChain));
assertIsDisplayed(ruleChainsPage.warningMessage());
assertThat(ruleChainsPage.warningMessage().getText())
.as("Text of warning message").isEqualTo(DELETE_RULE_CHAIN_WITH_PROFILE_MESSAGE);
}
@Epic("Rule chains smoke tests")
@Feature("Delete rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Remove the rule chain with device profile by mark in the checkbox and then click on the trash can icon in the menu that appears at the top")
public void removeSelectedProfileRuleChain() {
@ -148,11 +123,10 @@ public class DeleteRuleChainTest extends AbstractDriverBaseTest {
String deletedRuleChain = ruleChainsPage.deleteSelected("Thermostat");
ruleChainsPage.refreshBtn().click();
Assert.assertNotNull(ruleChainsPage.entity(deletedRuleChain));
Assert.assertTrue(ruleChainsPage.entity(deletedRuleChain).isDisplayed());
Assert.assertNotNull(ruleChainsPage.warningMessage());
Assert.assertTrue(ruleChainsPage.warningMessage().isDisplayed());
Assert.assertEquals(ruleChainsPage.warningMessage().getText(), DELETE_RULE_CHAIN_WITH_PROFILE_MESSAGE);
assertIsDisplayed(ruleChainsPage.entity(deletedRuleChain));
assertIsDisplayed(ruleChainsPage.warningMessage());
assertThat(ruleChainsPage.warningMessage().getText())
.as("Text of warning message").isEqualTo(DELETE_RULE_CHAIN_WITH_PROFILE_MESSAGE);
}
@Epic("Rule chains smoke tests")
@ -167,10 +141,10 @@ public class DeleteRuleChainTest extends AbstractDriverBaseTest {
jsClick(ruleChainsPage.deleteBtnFromView());
ruleChainsPage.warningPopUpYesBtn().click();
Assert.assertNotNull(ruleChainsPage.entity(deletedRuleChain));
Assert.assertNotNull(ruleChainsPage.warningMessage());
Assert.assertTrue(ruleChainsPage.warningMessage().isDisplayed());
Assert.assertEquals(ruleChainsPage.warningMessage().getText(), DELETE_RULE_CHAIN_WITH_PROFILE_MESSAGE);
assertIsDisplayed(ruleChainsPage.entity(deletedRuleChain));
assertIsDisplayed(ruleChainsPage.warningMessage());
assertThat(ruleChainsPage.warningMessage().getText())
.as("Text of warning message").isEqualTo(DELETE_RULE_CHAIN_WITH_PROFILE_MESSAGE);
}
@Epic("Rule chains smoke tests")
@ -178,12 +152,12 @@ public class DeleteRuleChainTest extends AbstractDriverBaseTest {
@Test(priority = 30, groups = "smoke")
@Description("Remove the rule chain by clicking on the trash icon in the right side of rule chain without refresh")
public void removeRuleChainByRightSideBtnWithoutRefresh() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
sideBarMenuView.ruleChainsBtn().click();
String deletedRuleChain = ruleChainsPage.deleteRuleChainTrash(ruleChainName);
Assert.assertTrue(ruleChainsPage.entityIsNotPresent(deletedRuleChain));
ruleChainsPage.assertEntityIsNotPresent(deletedRuleChain);
}
}

44
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/DeleteSeveralRuleChainsTest.java → msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/DeleteSeveralRuleChainsTest.java

@ -13,43 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.ruleChainsSmoke;
package org.thingsboard.server.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
import static org.thingsboard.server.msa.ui.utils.Const.ROOT_RULE_CHAIN_NAME;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultRuleChainPrototype;
public class DeleteSeveralRuleChainsTest extends AbstractDriverBaseTest {
@Feature("Delete several rule chains")
public class DeleteSeveralRuleChainsTest extends AbstractRuleChainTest {
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
}
@Epic("Rule chains smoke tests")
@Feature("Delete several rule chains")
@Test(priority = 10, groups = "smoke")
@Description("Remove several rule chains by mark in the checkbox and then click on the trash can icon in the menu " +
"that appears at the top")
public void canDeleteSeveralRuleChainsByTopBtn() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName + 1));
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
@ -57,16 +39,14 @@ public class DeleteSeveralRuleChainsTest extends AbstractDriverBaseTest {
ruleChainsPage.deleteSelected(2);
ruleChainsPage.refreshBtn().click();
Assert.assertTrue(ruleChainsPage.ruleChainsIsNotPresent(ruleChainName));
ruleChainsPage.assertRuleChainsIsNotPresent(ruleChainName);
}
@Epic("Rule chains smoke tests")
@Feature("Delete several rule chains")
@Test(priority = 10, groups = "smoke")
@Description("Remove several rule chains by mark all the rule chains on the page by clicking in the topmost checkbox" +
" and then clicking on the trash icon in the menu that appears")
public void selectAllRuleChain() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName + 1));
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
@ -76,11 +56,9 @@ public class DeleteSeveralRuleChainsTest extends AbstractDriverBaseTest {
ruleChainsPage.warningPopUpYesBtn().click();
ruleChainsPage.refreshBtn().click();
Assert.assertTrue(ruleChainsPage.ruleChainsIsNotPresent(ruleChainName));
ruleChainsPage.assertRuleChainsIsNotPresent(ruleChainName);
}
@Epic("Rule chains smoke tests")
@Feature("Delete several rule chains")
@Test(priority = 20, groups = "smoke")
@Description("Remove the root rule chain by mark in the checkbox and then click on the trash can icon in the menu " +
"that appears at the top")
@ -88,11 +66,9 @@ public class DeleteSeveralRuleChainsTest extends AbstractDriverBaseTest {
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.selectAllCheckBox().click();
Assert.assertFalse(ruleChainsPage.deleteBtn(ROOT_RULE_CHAIN_NAME).isEnabled());
assertIsDisable(ruleChainsPage.deleteBtn(ROOT_RULE_CHAIN_NAME));
}
@Epic("Rule chains smoke tests")
@Feature("Delete several rule chains")
@Test(priority = 20, groups = "smoke")
@Description("Remove the root rule chain by mark all the rule chains on the page by clicking in the topmost checkbox" +
" and then clicking on the trash icon in the menu that appears")
@ -103,8 +79,6 @@ public class DeleteSeveralRuleChainsTest extends AbstractDriverBaseTest {
ruleChainsPage.assertCheckBoxIsNotDisplayed(ROOT_RULE_CHAIN_NAME);
}
@Epic("Rule chains smoke tests")
@Feature("Delete several rule chains")
@Test(priority = 30, groups = "smoke")
@Description("Remove several rule chains by mark in the checkbox and then click on the trash can icon in the menu " +
"that appears at the top without refresh")
@ -116,6 +90,6 @@ public class DeleteSeveralRuleChainsTest extends AbstractDriverBaseTest {
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.deleteSelected(2);
Assert.assertTrue(ruleChainsPage.ruleChainsIsNotPresent(ruleChainName));
ruleChainsPage.assertRuleChainsIsNotPresent(ruleChainName);
}
}

36
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/MakeRuleChainRootTest.java → msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/MakeRuleChainRootTest.java

@ -13,39 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.ruleChainsSmoke;
package org.thingsboard.server.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
public class MakeRuleChainRootTest extends AbstractDriverBaseTest {
import static org.assertj.core.api.Assertions.assertThat;
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
}
@Feature("Make rule chain root")
public class MakeRuleChainRootTest extends AbstractRuleChainTest {
@AfterMethod
public void makeRoot() {
testRestClient.setRootRuleChain(getRuleChainByName("Root Rule Chain").getId());
setRootRuleChain("Root Rule Chain");
}
@Epic("Rule chains smoke tests")
@Feature("Make rule chain root")
@Test(priority = 10, groups = "smoke")
@Description("Make rule chain root by clicking on the 'Make rule chain root' icon in the right corner")
public void makeRuleChainRootByRightCornerBtn() {
@ -55,11 +39,9 @@ public class MakeRuleChainRootTest extends AbstractDriverBaseTest {
ruleChainsPage.makeRootBtn(ruleChain).click();
ruleChainsPage.warningPopUpYesBtn().click();
Assert.assertTrue(ruleChainsPage.rootCheckBoxEnable(ruleChain).isDisplayed());
assertIsDisplayed(ruleChainsPage.rootCheckBoxEnable(ruleChain));
}
@Epic("Rule chains smoke tests")
@Feature("Make rule chain root")
@Test(priority = 20, groups = "smoke")
@Description("Make rule chain root by clicking on the 'Make rule chain root' button in the entity view")
public void makeRuleChainRootFromView() {
@ -71,11 +53,9 @@ public class MakeRuleChainRootTest extends AbstractDriverBaseTest {
ruleChainsPage.warningPopUpYesBtn().click();
ruleChainsPage.closeEntityViewBtn().click();
Assert.assertTrue(ruleChainsPage.rootCheckBoxEnable(ruleChain).isDisplayed());
assertIsDisplayed(ruleChainsPage.rootCheckBoxEnable(ruleChain));
}
@Epic("Rule chains smoke tests")
@Feature("Make rule chain root")
@Test(priority = 30, groups = "smoke")
@Description("Make multiple root rule chains (only one rule chain can be root)")
public void multiplyRoot() {
@ -87,6 +67,6 @@ public class MakeRuleChainRootTest extends AbstractDriverBaseTest {
ruleChainsPage.warningPopUpYesBtn().click();
ruleChainsPage.closeEntityViewBtn().click();
Assert.assertEquals(ruleChainsPage.rootCheckBoxesEnable().size(), 1);
assertThat(ruleChainsPage.rootCheckBoxesEnable()).as("Enable only 1 root checkbox").hasSize(1);
}
}

66
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/OpenRuleChainTest.java

@ -0,0 +1,66 @@
/**
* 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.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import org.testng.annotations.Test;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
@Feature("Open rule chain")
public class OpenRuleChainTest extends AbstractRuleChainTest {
@Test(priority = 10, groups = "smoke")
@Description("Open the rule chain by clicking on its name")
public void openRuleChainByRightCornerBtn() {
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(EntityPrototypes.defaultRuleChainPrototype(ruleChainName));
RuleChain ruleChain = getRuleChainByName(ruleChainName);
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.entity(ruleChainName).click();
openRuleChainPage.setHeadName();
assertThat(urlContains(ruleChain.getUuidId().toString())).as("URL contains rule chain's ID").isTrue();
assertIsDisplayed(openRuleChainPage.headRuleChainName());
assertIsDisplayed(openRuleChainPage.inputNode());
assertThat(openRuleChainPage.getHeadName()).as("Head of opened rule chain page text").isEqualTo(ruleChainName);
}
@Test(priority = 10, groups = "smoke")
@Description("Open the rule chain by clicking on the 'Open rule chain' button in the entity view")
public void openRuleChainByViewBtn() {
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(EntityPrototypes.defaultRuleChainPrototype(ruleChainName));
RuleChain ruleChain = getRuleChainByName(ruleChainName);
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.detailsBtn(ruleChainName).click();
ruleChainsPage.openRuleChainFromViewBtn().click();
openRuleChainPage.setHeadName();
assertThat(ruleChain).as("Rule chain created").isNotNull();
assertThat(urlContains(ruleChain.getUuidId().toString())).as("URL contains rule chain's ID").isTrue();
assertIsDisplayed(openRuleChainPage.headRuleChainName());
assertIsDisplayed(openRuleChainPage.inputNode());
assertThat(openRuleChainPage.getHeadName()).as("Head of opened rule chain page text").isEqualTo(ruleChainName);
}
}

98
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/RuleChainEditMenuTest.java → msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/RuleChainEditMenuTest.java

@ -13,58 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.ruleChainsSmoke;
package org.thingsboard.server.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
import org.thingsboard.server.msa.ui.utils.DataProviderCredential;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.getRandomNumber;
import static org.thingsboard.server.msa.ui.base.AbstractBasePage.random;
import static org.thingsboard.server.msa.ui.utils.Const.EMPTY_RULE_CHAIN_MESSAGE;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultRuleChainPrototype;
public class RuleChainEditMenuTest extends AbstractDriverBaseTest {
@Feature("Edit rule chain")
public class RuleChainEditMenuTest extends AbstractRuleChainTest {
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
private String ruleChainName;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
}
@AfterMethod
public void delete() {
if (ruleChainName != null) {
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainName).getId());
ruleChainName = null;
}
}
@Epic("Rule chains smoke tests")
@Feature("Edit rule chain")
@Test(priority = 10, groups = "smoke")
@Description("Change name by edit menu")
public void changeName() {
String newRuleChainName = "Changed" + getRandomNumber();
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
this.ruleChainName = ruleChainName;
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.detailsBtn(ruleChainName).click();
@ -73,39 +45,33 @@ public class RuleChainEditMenuTest extends AbstractDriverBaseTest {
ruleChainsPage.editPencilBtn().click();
ruleChainsPage.changeNameEditMenu(newRuleChainName);
ruleChainsPage.doneBtnEditView().click();
this.ruleChainName = newRuleChainName;
ruleChainName = newRuleChainName;
ruleChainsPage.setHeaderName();
String nameAfter = ruleChainsPage.getHeaderName();
Assert.assertNotEquals(nameBefore, nameAfter);
Assert.assertEquals(newRuleChainName, nameAfter);
assertThat(nameAfter).as("The name has changed").isNotEqualTo(nameBefore);
assertThat(nameAfter).as("The name has changed correctly").isEqualTo(newRuleChainName);
}
@Epic("Rule chains smoke tests")
@Feature("Edit rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Delete name and save")
public void deleteName() {
String ruleChainName = ENTITY_NAME + random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
this.ruleChainName = ruleChainName;
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.detailsBtn(ruleChainName).click();
ruleChainsPage.editPencilBtn().click();
ruleChainsPage.changeNameEditMenu("");
Assert.assertFalse(ruleChainsPage.doneBtnEditViewVisible().isEnabled());
assertIsDisable(ruleChainsPage.doneBtnEditViewVisible());
}
@Epic("Rule chains smoke tests")
@Feature("Edit rule chain")
@Test(priority = 20, groups = "smoke")
@Description("Save only with space")
public void saveOnlyWithSpace() {
String ruleChainName = ENTITY_NAME +random();
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
this.ruleChainName = ruleChainName;
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.detailsBtn(ruleChainName).click();
@ -113,50 +79,44 @@ public class RuleChainEditMenuTest extends AbstractDriverBaseTest {
ruleChainsPage.changeNameEditMenu(" ");
ruleChainsPage.doneBtnEditView().click();
Assert.assertNotNull(ruleChainsPage.warningMessage());
Assert.assertTrue(ruleChainsPage.warningMessage().isDisplayed());
Assert.assertEquals(ruleChainsPage.warningMessage().getText(), EMPTY_RULE_CHAIN_MESSAGE);
assertIsDisplayed(ruleChainsPage.warningMessage());
assertThat(ruleChainsPage.warningMessage().getText()).as("Text of warning message").isEqualTo(EMPTY_RULE_CHAIN_MESSAGE);
}
@Epic("Rule chains smoke tests")
@Feature("Edit rule chain")
@Test(priority = 20, groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "editMenuDescription")
@Description("Write the description and save the changes/Change the description and save the changes/Delete the description and save the changes")
public void editDescription(String description, String newDescription, String finalDescription) {
String name = ENTITY_NAME + random();
testRestClient.postRuleChain(EntityPrototypes.defaultRuleChainPrototype(name, description));
ruleChainName = name;
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(EntityPrototypes.defaultRuleChainPrototype(ruleChainName, description));
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.detailsBtn(name).click();
ruleChainsPage.detailsBtn(ruleChainName).click();
ruleChainsPage.editPencilBtn().click();
ruleChainsPage.descriptionEntityView().sendKeys(newDescription);
ruleChainsPage.doneBtnEditView().click();
ruleChainsPage.setDescription();
Assert.assertEquals(ruleChainsPage.getDescription(), finalDescription);
assertThat(ruleChainsPage.getDescription()).as("The description changed correctly").isEqualTo(finalDescription);
}
@Epic("Rule chains smoke tests")
@Feature("Edit rule chain")
@Test(priority = 20, groups = "smoke")
@Test(priority = 20, groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "debugMode")
@Description("Enable debug mode/Disable debug mode")
public void debugMode() {
String ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
this.ruleChainName = ruleChainName;
public void debugMode(boolean debugMode) {
ruleChainName = ENTITY_NAME + random();
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName, debugMode));
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.detailsBtn(ruleChainName).click();
ruleChainsPage.editPencilBtn().click();
ruleChainsPage.debugCheckboxEdit().click();
ruleChainsPage.doneBtnEditView().click();
boolean debugMode = ruleChainsPage.debugCheckboxView().getAttribute("class").contains("selected");
ruleChainsPage.editPencilBtn().click();
ruleChainsPage.debugCheckboxEdit().click();
ruleChainsPage.doneBtnEditView().click();
Assert.assertFalse(ruleChainsPage.debugCheckboxView().getAttribute("class").contains("selected"), "Debug mode disable");
Assert.assertTrue(debugMode, "Debug mode enable");
if (debugMode) {
assertThat(ruleChainsPage.debugCheckboxView().getAttribute("class").contains("selected"))
.as("Debug mode is enable").isFalse();
} else {
assertThat(ruleChainsPage.debugCheckboxView().getAttribute("class").contains("selected"))
.as("Debug mode is enable").isTrue();
}
}
}
}

41
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/SearchRuleChainTest.java → msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/SearchRuleChainTest.java

@ -13,60 +13,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.ruleChainsSmoke;
package org.thingsboard.server.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
import org.thingsboard.server.msa.ui.utils.DataProviderCredential;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultRuleChainPrototype;
public class SearchRuleChainTest extends AbstractDriverBaseTest {
@Feature("Search rule chain")
public class SearchRuleChainTest extends AbstractRuleChainTest {
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
}
@Epic("Rule chains smoke tests")
@Feature("Search rule chain")
@Test(priority = 10, groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "ruleChainNameForSearchByFirstAndSecondWord")
@Description("Search rule chain by first word in the name/Search rule chain by second word in the name")
public void searchFirstWord(String namePath) {
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.searchEntity(namePath);
ruleChainsPage.setRuleChainName(0);
Assert.assertTrue(ruleChainsPage.getRuleChainName().contains(namePath));
ruleChainsPage.allNames().forEach(rc -> assertThat(rc.getText().contains(namePath))
.as("All entity contains search input").isTrue());
}
@Epic("Rule chains smoke tests")
@Feature("Search rule chain")
@Test(priority = 10, groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "nameForSearchBySymbolAndNumber")
@Description("Search rule chain by symbol in the name/Search rule chain by number in the name")
public void searchNumber(String name, String namePath) {
testRestClient.postRuleChain(defaultRuleChainPrototype(name));
ruleChainName = name;
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.searchEntity(namePath);
ruleChainsPage.setRuleChainName(0);
boolean ruleChainContainsNamePath = ruleChainsPage.getRuleChainName().contains(namePath);
testRestClient.deleteRuleChain(getRuleChainByName(name).getId());
Assert.assertTrue(ruleChainContainsNamePath);
ruleChainsPage.allNames().forEach(rc -> assertThat(rc.getText().contains(namePath))
.as("All entity contains search input").isTrue());
}
}

68
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/SortByNameTest.java → msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/SortByNameTest.java

@ -13,57 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.ruleChainsSmoke;
package org.thingsboard.server.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
import org.thingsboard.server.msa.ui.utils.DataProviderCredential;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultRuleChainPrototype;
public class SortByNameTest extends AbstractDriverBaseTest {
@Feature("Sort rule chain by name")
public class SortByNameTest extends AbstractRuleChainTest {
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
private String ruleChainName;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
}
@AfterMethod
public void delete() {
if (ruleChainName != null) {
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainName).getId());
ruleChainName = null;
}
}
@Epic("Rule chains smoke tests")
@Feature("Sort rule chain by name")
@Test(priority = 10, groups = "smoke", dataProviderClass = DataProviderCredential.class, dataProvider = "nameForSort")
@Description("Sort rule chain 'UP'")
public void specialCharacterUp(String ruleChainName) {
public void specialCharacterUp(String name) {
ruleChainName = name;
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
this.ruleChainName = ruleChainName;
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.sortByNameBtn().click();
ruleChainsPage.setRuleChainName(0);
Assert.assertEquals(ruleChainsPage.getRuleChainName(), ruleChainName);
assertThat(ruleChainsPage.getRuleChainName()).as("First rule chain after sort").isEqualTo(ruleChainName);
}
@Epic("Rule chains smoke tests")
@ -84,13 +58,13 @@ public class SortByNameTest extends AbstractDriverBaseTest {
ruleChainsPage.setRuleChainName(2);
String thirdRuleChain = ruleChainsPage.getRuleChainName();
testRestClient.deleteRuleChain(getRuleChainByName(ruleChain).getId());
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainNumber).getId());
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainSymbol).getId());
deleteRuleChainByName(ruleChain);
deleteRuleChainByName(ruleChainNumber);
deleteRuleChainByName(ruleChainSymbol);
Assert.assertEquals(firstRuleChain, ruleChainSymbol);
Assert.assertEquals(secondRuleChain, ruleChainNumber);
Assert.assertEquals(thirdRuleChain, ruleChain);
assertThat(firstRuleChain).as("First rule chain with symbol in name").isEqualTo(ruleChainSymbol);
assertThat(secondRuleChain).as("Second rule chain with number in name").isEqualTo(ruleChainNumber);
assertThat(thirdRuleChain).as("Third rule chain with number in name").isEqualTo(ruleChain);
}
@Epic("Rule chains smoke tests")
@ -105,7 +79,7 @@ public class SortByNameTest extends AbstractDriverBaseTest {
ruleChainsPage.sortByNameDown();
ruleChainsPage.setRuleChainName(ruleChainsPage.allNames().size() - 1);
Assert.assertEquals(ruleChainsPage.getRuleChainName(), ruleChainName);
assertThat(ruleChainsPage.getRuleChainName()).as("Last rule chain after sort").isEqualTo(ruleChainName);
}
@Epic("Rule chains smoke tests")
@ -127,12 +101,12 @@ public class SortByNameTest extends AbstractDriverBaseTest {
ruleChainsPage.setRuleChainName(lastIndex - 2);
String thirdRuleChain = ruleChainsPage.getRuleChainName();
testRestClient.deleteRuleChain(getRuleChainByName(ruleChain).getId());
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainNumber).getId());
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainSymbol).getId());
deleteRuleChainByName(ruleChain);
deleteRuleChainByName(ruleChainNumber);
deleteRuleChainByName(ruleChainSymbol);
Assert.assertEquals(firstRuleChain, ruleChainSymbol);
Assert.assertEquals(secondRuleChain, ruleChainNumber);
Assert.assertEquals(thirdRuleChain, ruleChain);
assertThat(firstRuleChain).as("First from the end rule chain with symbol in name").isEqualTo(ruleChainSymbol);
assertThat(secondRuleChain).as("Second from the end rule chain with number in name").isEqualTo(ruleChainNumber);
assertThat(thirdRuleChain).as("Third rule from the end chain with number in name").isEqualTo(ruleChain);
}
}
}

55
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/ruleChainsSmoke/SortByTimeTest.java → msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/tests/rulechainssmoke/SortByTimeTest.java

@ -13,70 +13,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.ui.tests.ruleChainsSmoke;
package org.thingsboard.server.msa.ui.tests.rulechainssmoke;
import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.ui.base.AbstractDriverBaseTest;
import org.thingsboard.server.msa.ui.pages.LoginPageHelper;
import org.thingsboard.server.msa.ui.pages.RuleChainsPageHelper;
import org.thingsboard.server.msa.ui.pages.SideBarMenuViewElements;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.ui.utils.Const.ENTITY_NAME;
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultRuleChainPrototype;
public class SortByTimeTest extends AbstractDriverBaseTest {
@Feature("Sort rule chain by time")
public class SortByTimeTest extends AbstractRuleChainTest {
private SideBarMenuViewElements sideBarMenuView;
private RuleChainsPageHelper ruleChainsPage;
private String ruleChainName;
@BeforeClass
public void login() {
new LoginPageHelper(driver).authorizationTenant();
sideBarMenuView = new SideBarMenuViewElements(driver);
ruleChainsPage = new RuleChainsPageHelper(driver);
}
@AfterMethod
public void delete() {
if (ruleChainName != null) {
testRestClient.deleteRuleChain(getRuleChainByName(ruleChainName).getId());
ruleChainName = null;
}
}
@Epic("Rule chains smoke tests")
@Feature("Sort rule chain by time")
@Test(priority = 10, groups = "smoke")
@Description("Sort rule chain 'DOWN'")
public void sortByTimeDown() {
String ruleChain = ENTITY_NAME;
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChain));
ruleChainName = ruleChain;
ruleChainName = ENTITY_NAME;
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.setSort();
String firstListElement = ruleChainsPage.getSort().get(ruleChainsPage.getSort().size() - 1);
String lastCreated = ruleChainsPage.createdTime().get(0).getText();
Assert.assertEquals(firstListElement, lastCreated);
Assert.assertNotNull(ruleChainsPage.createdTimeEntity(ruleChain, lastCreated));
assertThat(firstListElement).as("Last in list is last created").isEqualTo(lastCreated);
assertIsDisplayed(ruleChainsPage.createdTimeEntity(ruleChainName, lastCreated));
}
@Epic("Rule chains smoke tests")
@Feature("Sort rule chain by time")
@Test(priority = 10, groups = "smoke")
@Description("Sort rule chain 'UP'")
public void sortByTimeUp() {
String ruleChain = ENTITY_NAME;
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChain));
ruleChainName = ruleChain;
ruleChainName = ENTITY_NAME;
testRestClient.postRuleChain(defaultRuleChainPrototype(ruleChainName));
sideBarMenuView.ruleChainsBtn().click();
ruleChainsPage.sortByTimeBtn().click();
@ -84,7 +53,7 @@ public class SortByTimeTest extends AbstractDriverBaseTest {
String firstListElement = ruleChainsPage.getSort().get(ruleChainsPage.getSort().size() - 1);
String lastCreated = ruleChainsPage.createdTime().get(ruleChainsPage.createdTime().size() - 1).getText();
Assert.assertEquals(firstListElement, lastCreated);
Assert.assertNotNull(ruleChainsPage.createdTimeEntity(ruleChain, lastCreated));
assertThat(firstListElement).as("First in list is last created").isEqualTo(lastCreated);
assertIsDisplayed(ruleChainsPage.createdTimeEntity(ruleChainName, lastCreated));
}
}

7
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/DataProviderCredential.java

@ -149,4 +149,11 @@ public class DataProviderCredential {
{description, newDescription, description + newDescription},
{description, Keys.CONTROL + "A" + Keys.BACK_SPACE, ""}};
}
@DataProvider
public static Object[][] debugMode() {
return new Object[][]{
{false},
{true}};
}
}

7
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ui/utils/EntityPrototypes.java

@ -63,6 +63,13 @@ public class EntityPrototypes {
return ruleChain;
}
public static RuleChain defaultRuleChainPrototype(String entityName, boolean debugMode) {
RuleChain ruleChain = new RuleChain();
ruleChain.setName(entityName);
ruleChain.setDebugMode(debugMode);
return ruleChain;
}
public static DeviceProfile defaultDeviceProfile(String entityName) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName(entityName);

2
msa/black-box-tests/src/test/resources/all.xml

@ -34,7 +34,7 @@
</run>
</groups>
<packages>
<package name="org.thingsboard.server.msa.ui.tests.ruleChainsSmoke"/>
<package name="org.thingsboard.server.msa.ui.tests.rulechainssmoke"/>
</packages>
</test>
<test name="Smoke customers tests">

2
msa/black-box-tests/src/test/resources/smokesRuleChain.xml

@ -29,7 +29,7 @@
</run>
</groups>
<packages>
<package name="org.thingsboard.server.msa.ui.tests.ruleChainsSmoke"/>
<package name="org.thingsboard.server.msa.ui.tests.rulechainssmoke"/>
</packages>
</test>
</suite>

2
msa/black-box-tests/src/test/resources/uiTests.xml

@ -29,7 +29,7 @@
</run>
</groups>
<packages>
<package name="org.thingsboard.server.msa.ui.tests.ruleChainsSmoke"/>
<package name="org.thingsboard.server.msa.ui.tests.rulechainssmoke"/>
</packages>
</test>
<test name="Smoke customers tests">

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

3
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.api;
import org.thingsboard.server.common.data.plugin.ComponentScope;
import org.thingsboard.server.common.data.plugin.ComponentClusteringMode;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.rule.RuleChainType;
@ -38,6 +39,8 @@ public @interface RuleNode {
Class<? extends NodeConfiguration> configClazz();
ComponentClusteringMode clusteringMode() default ComponentClusteringMode.ENABLED;
boolean inEnabled() default true;
boolean outEnabled() default true;

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

14
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java

@ -92,12 +92,14 @@ public class TbLogNode implements TbNode {
boolean isStandard(TbLogNodeConfiguration conf) {
Objects.requireNonNull(conf, "node config is null");
final TbLogNodeConfiguration defaultConfig = new TbLogNodeConfiguration().defaultConfiguration();
switch (conf.getScriptLang()) {
case JS: return defaultConfig.getJsScript().equals(conf.getJsScript());
case TBEL: return defaultConfig.getTbelScript().equals(conf.getTbelScript());
default:
log.warn("No rule to define isStandard script for script language [{}], assuming that is non-standard", conf.getScriptLang());
return false;
if (conf.getScriptLang() == null || conf.getScriptLang().equals(ScriptLanguage.JS)) {
return defaultConfig.getJsScript().equals(conf.getJsScript());
} else if (conf.getScriptLang().equals(ScriptLanguage.TBEL)) {
return defaultConfig.getTbelScript().equals(conf.getTbelScript());
} else {
log.warn("No rule to define isStandard script for script language [{}], assuming that is non-standard", conf.getScriptLang());
return false;
}
}

10
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java

@ -35,7 +35,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
configClazz = EmptyNodeConfiguration.class,
relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "RPC Queued", "RPC Sent", "RPC Delivered", "RPC Successful", "RPC Timeout", "RPC Expired", "RPC Failed", "RPC Deleted",
"Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned",
"Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant",
"Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Alarm Assigned", "Alarm Unassigned", "Comment Created", "Comment Updated", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant",
"Relation Added or Updated", "Relation Deleted", "All Relations Deleted", "Timeseries Updated", "Timeseries Deleted"},
nodeDescription = "Route incoming messages by Message Type",
nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> etc. via corresponding chain, otherwise <b>Other</b> chain is used.",
@ -85,6 +85,14 @@ public class TbMsgTypeSwitchNode implements TbNode {
relationType = "Alarm Acknowledged";
} else if (msg.getType().equals(DataConstants.ALARM_CLEAR)) {
relationType = "Alarm Cleared";
} else if (msg.getType().equals(DataConstants.ALARM_ASSIGNED)) {
relationType = "Alarm Assigned";
} else if (msg.getType().equals(DataConstants.ALARM_UNASSIGNED)) {
relationType = "Alarm Unassigned";
} else if (msg.getType().equals(DataConstants.COMMENT_CREATED)) {
relationType = "Comment Created";
} else if (msg.getType().equals(DataConstants.COMMENT_UPDATED)) {
relationType = "Comment Updated";
} else if (msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE)) {
relationType = "RPC Request to Device";
} else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED_FROM_TENANT)) {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java

@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.credentials.BasicCredentials;
import org.thingsboard.rule.engine.credentials.ClientCredentials;
import org.thingsboard.rule.engine.credentials.CredentialsType;
import org.thingsboard.server.common.data.plugin.ComponentClusteringMode;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -47,6 +48,7 @@ import java.util.concurrent.TimeoutException;
type = ComponentType.EXTERNAL,
name = "mqtt",
configClazz = TbMqttNodeConfiguration.class,
clusteringMode = ComponentClusteringMode.USER_PREFERENCE,
nodeDescription = "Publish messages to the MQTT broker",
nodeDetails = "Will publish message payload to the MQTT broker with QoS <b>AT_LEAST_ONCE</b>.",
uiResources = {"static/rulenode/rulenode-core-config.js"},

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java

@ -16,9 +16,7 @@
package org.thingsboard.rule.engine.mqtt.azure;
import io.netty.handler.codec.mqtt.MqttVersion;
import io.netty.handler.ssl.SslContext;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.common.util.AzureIotHubUtil;
import org.thingsboard.mqtt.MqttClientConfig;
import org.thingsboard.rule.engine.api.RuleNode;
@ -26,12 +24,12 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.credentials.BasicCredentials;
import org.thingsboard.rule.engine.credentials.CertPemCredentials;
import org.thingsboard.rule.engine.credentials.ClientCredentials;
import org.thingsboard.rule.engine.credentials.CredentialsType;
import org.thingsboard.rule.engine.mqtt.TbMqttNode;
import org.thingsboard.rule.engine.mqtt.TbMqttNodeConfiguration;
import org.thingsboard.server.common.data.plugin.ComponentClusteringMode;
import org.thingsboard.server.common.data.plugin.ComponentType;
import javax.net.ssl.SSLException;
@ -41,6 +39,7 @@ import javax.net.ssl.SSLException;
type = ComponentType.EXTERNAL,
name = "azure iot hub",
configClazz = TbAzureIotHubNodeConfiguration.class,
clusteringMode = ComponentClusteringMode.SINGLETON,
nodeDescription = "Publish messages to the Azure IoT Hub",
nodeDetails = "Will publish message payload to the Azure IoT Hub with QoS <b>AT_LEAST_ONCE</b>.",
uiResources = {"static/rulenode/rulenode-core-config.js"},

13
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java

@ -110,6 +110,19 @@ public class TbLogNodeTest {
}
@Test
void backwardCompatibility_whenScriptLangIsNull() throws TbNodeException {
TbLogNodeConfiguration config = new TbLogNodeConfiguration().defaultConfiguration();
TbLogNode node = spy(new TbLogNode());
TbNodeConfiguration tbNodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config));
TbContext ctx = mock(TbContext.class);
node.init(ctx, tbNodeConfiguration);
assertThat(node.isStandard(config)).as("Script is standard for language JS").isTrue();
verify(node, never()).createScriptEngine(any(), any());
verify(ctx, never()).createScriptEngine(any(), anyString());
}
@Test
void givenScriptEngineEnum_whenNewAdded_thenFailed() {
assertThat(ScriptLanguage.values().length).as("only two ScriptLanguage supported").isEqualTo(2);

1
ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts

@ -467,6 +467,7 @@ export class ImportExportService {
const ruleChainNode: RuleNode = {
name: '',
debugMode: false,
singletonMode: false,
type: 'org.thingsboard.rule.engine.flow.TbRuleChainInputNode',
configuration: {
ruleChainId: ruleChainConnection.targetRuleChainId.id

13
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html

@ -24,7 +24,7 @@
<form [formGroup]="ruleNodeFormGroup" class="mat-padding">
<fieldset [disabled]="(isLoading$ | async) || !isEdit || isReadOnly">
<section>
<section fxLayout="column" fxLayout.gt-sm="row">
<section class="title-row">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>rulenode.name</mat-label>
<input matInput formControlName="name" required>
@ -36,9 +36,14 @@
{{ 'rulenode.name-max-length' | translate }}
</mat-error>
</mat-form-field>
<mat-checkbox formControlName="debugMode">
{{ 'rulenode.debug-mode' | translate }}
</mat-checkbox>
<section class="node-setting">
<mat-slide-toggle formControlName="debugMode">
{{ 'rulenode.debug-mode' | translate }}
</mat-slide-toggle>
<mat-slide-toggle *ngIf="isSingletonEditAllowed()" formControlName="singletonMode">
{{ 'rulenode.singleton-mode' | translate }}
</mat-slide-toggle >
</section>
</section>
<tb-rule-node-config #ruleNodeConfigComponent
formControlName="configuration"

22
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.scss

@ -13,8 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import './../scss/constants';
:host {
form {
overflow-x: hidden !important;
}
.title-row {
display: flex;
flex-direction: column;
.node-setting {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 5px;
}
@media #{$mat-gt-sm} {
flex-direction: row;
gap: 8px;
.node-setting {
margin-top: 5px;
}
}
}
}

6
ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts

@ -26,6 +26,7 @@ import { RuleChainService } from '@core/http/rule-chain.service';
import { RuleNodeConfigComponent } from './rule-node-config.component';
import { Router } from '@angular/router';
import { RuleChainType } from '@app/shared/models/rule-chain.models';
import { ComponentClusteringMode } from '@shared/models/component-descriptor.models';
@Component({
selector: 'tb-rule-node',
@ -78,6 +79,7 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
this.ruleNodeFormGroup = this.fb.group({
name: [this.ruleNode.name, [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*'), Validators.maxLength(255)]],
debugMode: [this.ruleNode.debugMode, []],
singletonMode: [this.ruleNode.singletonMode, []],
configuration: [this.ruleNode.configuration, [Validators.required]],
additionalInfo: this.fb.group(
{
@ -130,4 +132,8 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O
}
}
}
isSingletonEditAllowed() {
return this.ruleNode.component.clusteringMode === ComponentClusteringMode.USER_PREFERENCE;
}
}

13
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts

@ -89,6 +89,7 @@ import { DebugEventType, EventType } from '@shared/models/event.models';
import { MatMiniFabButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { VersionControlComponent } from '@home/components/vc/version-control.component';
import { ComponentClusteringMode } from '@shared/models/component-descriptor.models';
import Timeout = NodeJS.Timeout;
@Component({
@ -469,6 +470,7 @@ export class RuleChainPageComponent extends PageComponent
component: ruleNodeComponent,
name: '',
nodeClass: desc.nodeClass,
singletonMode: ruleNodeComponent.clusteringMode !== ComponentClusteringMode.ENABLED,
icon,
iconUrl,
x: 30,
@ -554,6 +556,7 @@ export class RuleChainPageComponent extends PageComponent
additionalInfo: ruleNode.additionalInfo,
configuration: ruleNode.configuration,
debugMode: ruleNode.debugMode,
singletonMode: ruleNode.singletonMode,
x: Math.round(ruleNode.additionalInfo.layoutX),
y: Math.round(ruleNode.additionalInfo.layoutY),
component,
@ -912,7 +915,8 @@ export class RuleChainPageComponent extends PageComponent
name: node.name,
configuration: deepClone(node.configuration),
additionalInfo: node.additionalInfo ? deepClone(node.additionalInfo) : {},
debugMode: node.debugMode
debugMode: node.debugMode,
singletonMode: node.singletonMode
};
if (minX === null) {
minX = node.x;
@ -983,7 +987,8 @@ export class RuleChainPageComponent extends PageComponent
name: outputEdge.label,
configuration: {},
additionalInfo: {},
debugMode: false
debugMode: false,
singletonMode: false
};
outputNode.additionalInfo.layoutX = Math.round(destNode.x);
outputNode.additionalInfo.layoutY = Math.round(destNode.y);
@ -1029,6 +1034,7 @@ export class RuleChainPageComponent extends PageComponent
ruleChainId: ruleChain.id.id
},
debugMode: false,
singletonMode: false,
x: Math.round(ruleChainNodeX),
y: Math.round(ruleChainNodeY),
nodeClass: descriptor.nodeClass,
@ -1420,7 +1426,8 @@ export class RuleChainPageComponent extends PageComponent
name: node.name,
configuration: node.configuration,
additionalInfo: node.additionalInfo ? node.additionalInfo : {},
debugMode: node.debugMode
debugMode: node.debugMode,
singletonMode: node.singletonMode
};
ruleNode.additionalInfo.layoutX = Math.round(node.x);
ruleNode.additionalInfo.layoutY = Math.round(node.y);

8
ui-ngx/src/app/shared/models/audit-log.models.ts

@ -47,8 +47,8 @@ export enum ActionType {
RELATIONS_DELETED = 'RELATIONS_DELETED',
ALARM_ACK = 'ALARM_ACK',
ALARM_CLEAR = 'ALARM_CLEAR',
ALARM_ASSIGN = 'ALARM_ASSIGN',
ALARM_UNASSIGN = 'ALARM_UNASSIGN',
ALARM_ASSIGNED = 'ALARM_ASSIGNED',
ALARM_UNASSIGNED = 'ALARM_UNASSIGNED',
ADDED_COMMENT = 'ADDED_COMMENT',
UPDATED_COMMENT = 'UPDATED_COMMENT',
DELETED_COMMENT = 'DELETED_COMMENT',
@ -90,8 +90,8 @@ export const actionTypeTranslations = new Map<ActionType, string>(
[ActionType.RELATIONS_DELETED, 'audit-log.type-relations-delete'],
[ActionType.ALARM_ACK, 'audit-log.type-alarm-ack'],
[ActionType.ALARM_CLEAR, 'audit-log.type-alarm-clear'],
[ActionType.ALARM_ASSIGN, 'audit-log.type-alarm-assign'],
[ActionType.ALARM_UNASSIGN, 'audit-log.type-alarm-unassign'],
[ActionType.ALARM_ASSIGNED, 'audit-log.type-alarm-assign'],
[ActionType.ALARM_UNASSIGNED, 'audit-log.type-alarm-unassign'],
[ActionType.ADDED_COMMENT, 'audit-log.type-added-comment'],
[ActionType.UPDATED_COMMENT, 'audit-log.type-updated-comment'],
[ActionType.DELETED_COMMENT, 'audit-log.type-deleted-comment'],

7
ui-ngx/src/app/shared/models/component-descriptor.models.ts

@ -30,9 +30,16 @@ export enum ComponentScope {
TENANT = 'TENANT'
}
export enum ComponentClusteringMode {
USER_PREFERENCE = 'USER_PREFERENCE',
ENABLED = 'ENABLED',
SINGLETON = 'SINGLETON'
}
export interface ComponentDescriptor {
type: ComponentType | RuleNodeType;
scope?: ComponentScope;
clusteringMode: ComponentClusteringMode;
name: string;
clazz: string;
configurationDescriptor?: any;

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'
}
]
]);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save